Compare commits

...

26 Commits

Author SHA1 Message Date
cedric d2b779f8de feat: config Plausible
Déploiement FRONT PROD / check (push) Successful in 2m7s
Déploiement FRONT PROD / deploy (push) Successful in 46s
2026-06-11 18:33:42 +04:00
cedric 9adc045a98 chore: add next-plausible lib 2026-06-11 18:33:31 +04:00
cedric f500b2eaa1 fix: change labola repo 2026-06-11 17:56:23 +04:00
cedric b018c2a917 feat: change language order 2026-06-11 17:56:01 +04:00
cedric ca9ba4cb10 feat: add AnVedette component
Déploiement FRONT PROD / check (push) Successful in 2m5s
Déploiement FRONT PROD / deploy (push) Successful in 21s
2026-06-08 21:46:18 +04:00
cedric c113a2547f fix: typo
Déploiement FRONT PROD / check (push) Successful in 2m14s
Déploiement FRONT PROD / deploy (push) Successful in 21s
2026-06-08 01:39:25 +04:00
cedric 6b54f13b3f feat: add image to paroles route 2026-06-08 01:37:59 +04:00
cedric 486a852195 feat: optimize cover in search
Déploiement FRONT PROD / check (push) Successful in 2m0s
Déploiement FRONT PROD / deploy (push) Successful in 21s
2026-06-08 01:25:58 +04:00
cedric a51744e941 feat: add cover to teks page
Déploiement FRONT PROD / check (push) Successful in 2m6s
Déploiement FRONT PROD / deploy (push) Successful in 23s
2026-06-08 01:19:30 +04:00
cedric 13d60a1b32 feat: optimize covers display
Déploiement FRONT PROD / check (push) Successful in 2m7s
Déploiement FRONT PROD / deploy (push) Successful in 30s
2026-06-08 00:52:23 +04:00
cedric 4955327334 fix: display audio length & reset curretTime at the end
Déploiement FRONT PROD / check (push) Successful in 2m19s
Déploiement FRONT PROD / deploy (push) Successful in 22s
2026-06-02 13:36:41 +04:00
cedric 57eeffc8f7 fix: replace publishedAt by createdAt in TeksKat
Déploiement FRONT PROD / check (push) Successful in 2m12s
Déploiement FRONT PROD / deploy (push) Successful in 22s
2026-06-02 09:57:10 +04:00
cedric a09b836218 fix: change Range audio files
Déploiement FRONT PROD / check (push) Successful in 2m6s
Déploiement FRONT PROD / deploy (push) Successful in 22s
2026-06-02 09:47:30 +04:00
cedric 38deb84be8 fix: change theme mode
Déploiement FRONT PROD / check (push) Successful in 2m2s
Déploiement FRONT PROD / deploy (push) Successful in 20s
2026-06-02 09:12:15 +04:00
cedric 2dc0e7933c feat: add pawol to player
Déploiement FRONT PROD / check (push) Successful in 2m9s
Déploiement FRONT PROD / deploy (push) Successful in 22s
2026-06-02 02:55:16 +04:00
cedric 0e2bcb17b2 feat: improve download
Déploiement FRONT PROD / check (push) Successful in 2m0s
Déploiement FRONT PROD / deploy (push) Successful in 21s
2026-06-01 01:19:11 +04:00
cedric cd3cc2a697 fix: explicit download
Déploiement FRONT PROD / check (push) Successful in 2m10s
Déploiement FRONT PROD / deploy (push) Successful in 21s
2026-06-01 00:52:41 +04:00
cedric 7161070435 feat: add cache to audio meta 2026-06-01 00:49:49 +04:00
cedric cb37979fc4 fix: check file info without download all file
Déploiement FRONT PROD / check (push) Successful in 2m0s
Déploiement FRONT PROD / deploy (push) Successful in 20s
2026-06-01 00:28:11 +04:00
cedric 9f8e60d56f feat: improve files info
Déploiement FRONT PROD / check (push) Successful in 2m11s
Déploiement FRONT PROD / deploy (push) Successful in 47s
2026-06-01 00:19:53 +04:00
cedric d02d946cfb remove GADE from rezo lis
Déploiement FRONT PROD / check (push) Successful in 1m58s
Déploiement FRONT PROD / deploy (push) Successful in 20s
2026-05-18 18:57:23 +04:00
cedric eef02431b8 update package metadata
Déploiement FRONT PROD / check (push) Successful in 2m6s
Déploiement FRONT PROD / deploy (push) Successful in 20s
2026-05-18 18:32:15 +04:00
cedric e05c61f0fe update git links to labola 2026-05-18 18:32:15 +04:00
cedric 67aa60a53b switch social link from codeberg to gitea 2026-05-18 18:32:15 +04:00
cedric 860bfe74df add gitea icon 2026-05-18 18:32:15 +04:00
cedric 921318f92f Merge pull request 'CI/CD & ajustements - Mise en place des workflows Gitea Actions' (#1) from dev into master
Déploiement FRONT PROD / check (push) Successful in 2m2s
Déploiement FRONT PROD / deploy (push) Successful in 25s
Reviewed-on: #1
2026-05-16 08:17:47 +00:00
24 changed files with 549 additions and 142 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ NEXT_PUBLIC_GADE_USERNAME=
NEXT_PUBLIC_YOUTUBE_USERNAME=
NEXT_PUBLIC_TELEGRAM_GROUP=
NEXT_PUBLIC_XMPP=
NEXT_PUBLIC_CODEBERG=
NEXT_PUBLIC_GIT=
# DOMAIN IMAGE
NEXT_PUBLIC_DOMAINS_IMAGE="localhost:1337 strapi.mondomaine.com"
+1 -1
View File
@@ -10,7 +10,7 @@
## Prérequis
- Node >= 20
- [API](https://codeberg.org/OKI/api.pawol.nu)
- [API](https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/api.pawol.nu)
## Variables d'environement
- Copier le contenu du fichier `.env.sample` dans un nouveau fichier `.env`
+1 -1
View File
@@ -24,7 +24,7 @@ export async function generateMetadata(props) {
const {slug} = params
const anAwtis = await jwennAwtis(slug)
const title = `OKI | ${anAwtis.alias} - Paroles et Traductions`
const title = `PAWÒL-NU | ${anAwtis.alias}`
const description = `${anAwtis.alias}${anAwtis?.biographie ? ` : ${anAwtis?.biographie.slice(0, 100)}...` : ''}`
const url = `${siteUrl}/awtis/${slug}`
+3 -3
View File
@@ -11,10 +11,10 @@ import {jwennAwtisPajinasyon} from '../../lib/oki-api'
import Footer from '../../components/footer'
export const metadata = {
title: 'OKI | Awtis - Liste des artistes',
title: 'PAWÒL-NU | Artistes',
description: 'Liste des artistes ayant une ou plusieurs œuvres présentes sur le site.',
openGraph: {
title: 'OKI | Awtis - Liste des artistes',
title: 'PAWÒL-NU | Artistes',
description: 'Liste des artistes ayant une ou plusieurs œuvres présentes sur le site.',
url: 'https://pawol.nu/sipote',
siteName: 'PAWÒL-NU. Paroles et traductions.',
@@ -31,7 +31,7 @@ export const metadata = {
twitter: {
site: '@OrganisationKA',
card: 'summary_large_image',
title: 'OKI | Awtis - Liste des artistes',
title: 'PAWÒL-NU | Artistes',
description: 'Liste des artistes ayant une ou plusieurs œuvres présentes sur le site.',
creator: '@OrganisationKA',
images: {
+10 -1
View File
@@ -1,3 +1,4 @@
import Script from 'next/script'
import TopLoader from '../components/top-loader'
import Navigasyon from '../components/navigasyon'
import ThemeRegistry from './theme-registy'
@@ -50,9 +51,17 @@ const jsonLd = {
location: 'Guadeloupe'
}
export default async function RootLayout({children, Session}) {
export default async function RootLayout({children}) {
return (
<html lang='fr' suppressHydrationWarning>
<head>
<Script
defer
data-domain='pawol.nu'
src='https://plausible.io/js/pa-3sQidCSfiSOXQUh-4La0T.js'
strategy='afterInteractive'
/>
</head>
<body>
<TopLoader color='#ffeb3b' />
<ThemeRegistry>
+6 -4
View File
@@ -1,32 +1,34 @@
import Box from '@mui/material/Box'
import Container from '@mui/material/Container'
import {notFound} from 'next/navigation'
import {jwennStats} from '../lib/oki-api'
import {jwennStats, jwennDenyeTeks} from '../lib/oki-api'
import Statistik from '../components/akey/statistik'
import Akey from '../components/akey'
import AnVedette from '../components/akey/an-vedette'
import okiLogo from '../public/logo-512x512.png'
import Footer from '../components/footer'
import Aso from '../components/akey/aso'
async function jwennDone() {
const statistik = await jwennStats()
const [statistik, denyeTeks] = await Promise.all([jwennStats(), jwennDenyeTeks()])
if (!statistik) {
notFound()
}
return statistik
return {statistik, dernierTeks: denyeTeks?.[0]}
}
export default async function Page() {
const statistik = await jwennDone()
const {statistik, dernierTeks} = await jwennDone()
return (
<Box sx={{display: 'flex', flexDirection: 'column', minHeight: '100vh'}}>
<Akey logo={okiLogo} />
<Container sx={{flexGrow: 100}}>
{dernierTeks && <AnVedette teks={dernierTeks} />}
<Statistik statistik={statistik} />
<Aso />
</Container>
+1 -1
View File
@@ -27,7 +27,7 @@ export async function generateMetadata(props) {
const anTeks = await jwennAnTeks(slug)
const awtis = anTeks?.artistes?.length === 1 ? anTeks?.artistes[0].alias : getAlias(anTeks.artistes, anTeks.prioriteArtistes)
const title = `OKI | ${awtis} - ${anTeks.titre} | Paroles et Traductions`
const title = `PAWÒL-NU | ${awtis} - ${anTeks.titre}`
const description = `Paroles de « ${anTeks?.titre} » : ${anTeks?.transcription.slice(0, 100)}...`
const url = `${siteUrl}/paroles/${slug}`
+36
View File
@@ -2,9 +2,13 @@ import Box from '@mui/material/Box'
import {notFound} from 'next/navigation'
import {jwennDenyeTeks} from '../../lib/oki-api'
import {formatKuveti} from '../../lib/kuveti'
import DenyeTeks from '../../components/teks/denye-teks'
import Footer from '../../components/footer'
const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'
async function jwennDone() {
const denyeTeks = await jwennDenyeTeks()
@@ -15,6 +19,38 @@ async function jwennDone() {
return denyeTeks
}
export async function generateMetadata() {
const denyeTeks = await jwennDone()
const couverture = formatKuveti(denyeTeks[0]?.couverture)
const imageUrl = couverture?.url ? `${apiUrl}${couverture.url}` : `${siteUrl}/logo-512x512.png`
const imageWidth = couverture?.width || 512
const imageHeight = couverture?.height || 512
const songList = denyeTeks.slice(0, 3).map(t => `${t.artistes[0]?.alias} ${t.titre}`).join(', ')
const description = `Derniers morceaux : ${songList}`
return {
title: 'PAWÒL-NU | Derniers morceaux',
description,
openGraph: {
title: 'PAWÒL-NU | Derniers morceaux',
description,
url: `${siteUrl}/paroles`,
siteName: 'PAWÒL-NU. Paroles et traductions.',
images: [{url: imageUrl, width: imageWidth, height: imageHeight}],
locale: 'fr_FR',
type: 'website',
},
twitter: {
site: '@OrganisationKA',
card: 'summary_large_image',
title: 'PAWÒL-NU | Derniers morceaux',
description,
creator: '@OrganisationKA',
images: {url: imageUrl, alt: 'Couverture du dernier morceau publié'},
},
}
}
export default async function PawolPaj() {
const denyeTeks = await jwennDone()
+67
View File
@@ -0,0 +1,67 @@
'use client'
import PropTypes from 'prop-types'
import Card from '@mui/material/Card'
import CardActionArea from '@mui/material/CardActionArea'
import CardContent from '@mui/material/CardContent'
import CardMedia from '@mui/material/CardMedia'
import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
import Chip from '@mui/material/Chip'
import Link from 'next/link'
import {getAlias} from '../../lib/utils/format'
import {formatKuveti} from '../../lib/kuveti'
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
const noImageUrl = 'https://place-hold.it/600x600?text=Indisponible'
export default function AnVedette({teks}) {
const {titre, artistes, annee, couverture, slug} = teks
const aliases = getAlias(artistes, teks.prioriteArtistes)
const fmt = formatKuveti(couverture)
const imageUrl = fmt?.url ? `${IMAGE_URL}${fmt.url}` : noImageUrl
return (
<Box sx={{mb: 4}}>
<Chip
label='À LA UNE'
size='small'
color='primary'
sx={{mb: 1.5, fontWeight: 'bold', letterSpacing: 1}}
/>
<Card sx={{borderRadius: 2, overflow: 'hidden'}}>
<CardActionArea component={Link} href={`/paroles/${slug}`}>
<CardMedia
component='img'
image={imageUrl}
alt={titre}
sx={{
width: '100%',
aspectRatio: {xs: '1 / 1', sm: '16 / 9'},
objectFit: 'cover',
maxHeight: {sm: 420}
}}
/>
<CardContent>
<Typography variant='h5' component='h2' sx={{fontWeight: 'bold', mb: 0.5}}>
{titre}
</Typography>
<Typography variant='body1' color='text.secondary'>
{aliases}
</Typography>
{annee && (
<Typography variant='body2' color='text.secondary'>
{annee}
</Typography>
)}
</CardContent>
</CardActionArea>
</Card>
</Box>
)
}
AnVedette.propTypes = {
teks: PropTypes.object.isRequired
}
+3 -3
View File
@@ -51,7 +51,7 @@ export default function AwtisDetay({anAwtis}) {
</Box>
<Box sx={{justifyContent: 'center', display: 'flex', marginBottom: 2}}>
<Avatar
src={`${photo?.url ? `${IMAGE_URL}${photo?.url}` : noImageUrl}`}
src={photo?.url ? `${IMAGE_URL}${photo?.formats?.small?.url || photo?.formats?.thumbnail?.url || photo?.url}` : noImageUrl}
alt={`Photo ${alias}`}
sx={{width: 200, height: 200, border: `2px solid ${green[500]}`}}
/>
@@ -88,7 +88,7 @@ export default function AwtisDetay({anAwtis}) {
<AccordionDetails sx={{paddingInline: 0}}>
{sortedTeks.map(anPawol => {
const {couverture} = anPawol
const kuvetiFormat = formatKuveti(couverture)
const kuvetiFormat = couverture?.formats?.thumbnail || formatKuveti(couverture)
return (
<Box key={anPawol.id} sx={{paddingBlock: 0.5}}>
@@ -105,7 +105,7 @@ export default function AwtisDetay({anAwtis}) {
<Box>
<Typography gutterBottom textalign='center' variant='body1' component='h2'><strong>Parole</strong></Typography>
<Paper sx={{height: '100%', paddingBlock: 2}}>
<MizikLyen anPawol={paroles[0]} kuveti={formatKuveti(paroles[0].couverture)} />
<MizikLyen anPawol={paroles[0]} kuveti={paroles[0].couverture?.formats?.thumbnail || formatKuveti(paroles[0].couverture)} />
</Paper>
</Box>
)
+2 -1
View File
@@ -66,7 +66,8 @@ export default function AwtisKat({artiste}) {
className={classes.media}
component='img'
alt={alias}
image={`${photo?.url ? `${IMAGE_URL}${photo?.url}` : noImageUrl}`}
image={`${photo?.url ? `${IMAGE_URL}${photo?.formats?.thumbnail?.url || photo?.url}` : noImageUrl}`}
loading='lazy'
title={alias}
/>
<CardContent>
+1 -1
View File
@@ -79,7 +79,7 @@ export default function ChecheAwtis() {
<Avatar
style={{ marginRight: 8 }}
alt={option?.alias}
src={`${IMAGE_URL}${option?.photo?.formats?.thumbnail?.url}`}
src={`${IMAGE_URL}${option?.photo?.formats?.thumbnail?.url || option?.photo?.url || ''}`}
/>
{option?.alias}
</li>
+1 -1
View File
@@ -34,7 +34,7 @@ export default function MizikLis({niAwtis, paroles, meteEsMobilOuve}) {
itemContent={index => {
const anPawol = pawol[index]
const {couverture} = anPawol
const kuvetiFormat = formatKuveti(couverture)
const kuvetiFormat = couverture?.formats?.thumbnail || formatKuveti(couverture)
return (
<MizikLyen niAwtis={niAwtis} anPawol={anPawol} kuveti={kuvetiFormat} slug={params.slug} meteEsMobilOuve={meteEsMobilOuve} />
+1 -1
View File
@@ -95,7 +95,7 @@ export default function Cgu() {
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
<strong>Git</strong> : <Link target='_blank' rel='noreferrer' href='https://codeberg.org/OKI'><strong>codeberg.org/OKI</strong></Link>
<strong>Git</strong> : <Link target='_blank' rel='noreferrer' href='https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE'><strong>LABOLA - OKI</strong></Link>
</ListItemText>
</ListItem>
</List>
+149 -70
View File
@@ -1,8 +1,11 @@
import {useState, useEffect, useRef} from 'react'
import PropTypes from 'prop-types'
import List from '@mui/material/List'
import ListSubheader from '@mui/material/ListSubheader'
import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
import Skeleton from '@mui/material/Skeleton'
import LinearProgress from '@mui/material/LinearProgress'
import {useTheme, useColorScheme, styled} from '@mui/material/styles'
import Table from '@mui/material/Table'
import TableHead from '@mui/material/TableHead'
@@ -11,12 +14,12 @@ import TableCell, {tableCellClasses} from '@mui/material/TableCell'
import TableRow from '@mui/material/TableRow'
import TableContainer from '@mui/material/TableContainer'
import Paper from '@mui/material/Paper'
import FileSaver from 'file-saver'
import DescriptionIcon from '@mui/icons-material/Description'
import LibraryMusicIcon from '@mui/icons-material/LibraryMusic'
import {Link} from '@mui/material'
const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
const audioMetaCache = {}
const StyledTableCell = styled(TableCell)(({theme}) => ({
[`&.${tableCellClasses.head}`]: {
@@ -49,6 +52,9 @@ function formatSize(size) {
export default function FilesList({files}) {
const theme = useTheme()
const {mode} = useColorScheme()
const [audioMeta, setAudioMeta] = useState(audioMetaCache)
const [downloading, setDownloading] = useState({})
const controllersRef = useRef({})
const musicFiles = files.filter(file => file.mime.startsWith('audio'))
const pdfFiles = files.filter(file => file.mime === 'application/pdf')
@@ -63,84 +69,138 @@ export default function FilesList({files}) {
return extensionOrder[a.ext.toLowerCase()] - extensionOrder[b.ext.toLowerCase()]
})
const handleClick = (e, url, fileName) => {
e.stopPropagation()
FileSaver.saveAs(url, fileName)
useEffect(() => {
const audioFiles = files.filter(f => f.mime.startsWith('audio'))
if (audioFiles.length === 0) return
let cancelled = false
async function fetchAllMeta() {
const mm = await import('music-metadata-browser')
const results = {}
await Promise.all(
audioFiles.map(async file => {
if (file.id in audioMetaCache) {
results[file.id] = audioMetaCache[file.id]
return
}
const getQuality = (extension, caption) => {
switch (extension) {
case '.ogg':
case '.aac':
case '.mp3':
return (
<Typography sx={{
backgroundColor: '#393940',
color: '#fff',
borderRadius: '0.66rem',
border: mode === 'dark' ? '1px solid #fff' : 'none',
fontSize: '0.75rem',
letterSpacing: '0.1rem',
padding: '0.2515rem 0.6707rem',
textTransform: 'uppercase',
fontWeight: 'bold'
}}
>Faible</Typography>
try {
const response = await fetch(`${apiUrl}${file.url}`, {
headers: {Range: 'bytes=0-32767'},
})
const buffer = await response.arrayBuffer()
const meta = await mm.parseBuffer(new Uint8Array(buffer), {mimeType: file.mime, skipCovers: true})
audioMetaCache[file.id] = meta.format
results[file.id] = meta.format
} catch {
audioMetaCache[file.id] = null
results[file.id] = null
}
})
)
case '.flac':
if (caption === 'MAX') {
if (!cancelled) setAudioMeta(results)
}
fetchAllMeta()
return () => {
cancelled = true
}
}, [files])
const losslessFormats = new Set(['.flac', '.wav', '.aiff', '.alac'])
const qualityBadge = ext => {
const isHaute = losslessFormats.has(ext.toLowerCase())
return (
<Typography sx={{
backgroundColor: '#332619',
color: '#ffbe7d',
<Typography variant='caption' sx={{
backgroundColor: isHaute ? '#07332f' : '#393940',
color: isHaute ? '#21feec' : '#fff',
borderRadius: '0.66rem',
border: mode === 'dark' ? '1px solid #ffbe7d' : 'none',
fontSize: '0.75rem',
letterSpacing: '0.1rem',
padding: '0.2515rem 0.6707rem',
textTransform: 'uppercase',
border: mode === 'dark' ? `1px solid ${isHaute ? '#21feec' : '#fff'}` : 'none',
padding: '0.15rem 0.5rem',
fontWeight: 'bold',
textAlign: 'center'
}}
>{caption}</Typography>
letterSpacing: '0.05rem',
textTransform: 'uppercase',
}}>{isHaute ? 'Haute' : 'Faible'}</Typography>
)
}
if (caption === 'HAUTE') {
return (
<Typography sx={{
backgroundColor: '#07332f',
color: '#21feec',
borderRadius: '0.66rem',
border: mode === 'dark' ? '1px solid #21feec' : 'none',
fontSize: '0.75rem',
letterSpacing: '0.1rem',
padding: '0.2515rem 0.6707rem',
textTransform: 'uppercase',
fontWeight: 'bold'
}}
>{caption}</Typography>
)
const renderMeta = file => {
const format = file.ext.replace('.', '').toUpperCase()
if (!(file.id in audioMeta)) {
return <Skeleton variant='text' width={80} />
}
const meta = audioMeta[file.id]
const sampleRate = meta?.sampleRate ? `${meta.sampleRate / 1000} kHz` : null
const bitDepth = meta?.bitsPerSample ? `${meta.bitsPerSample} bits` : null
const bitrate = meta?.bitrate ? `${Math.round(meta.bitrate / 1000)} kbps` : null
const details = [sampleRate, bitDepth ?? bitrate].filter(Boolean).join(' · ')
return (
<Typography sx={{
backgroundColor: '#07332f',
color: '#21feec',
borderRadius: '0.66rem',
border: mode === 'dark' ? '1px solid #21feec' : 'none',
fontSize: '0.75rem',
letterSpacing: '0.1rem',
padding: '0.2515rem 0.6707rem',
textTransform: 'uppercase',
fontWeight: 'bold'
}}
>Haute</Typography>
<Box display='flex' flexDirection='column' gap={0.5}>
<Box display='flex' alignItems='center' gap={1}>
{qualityBadge(file.ext)}
<Typography variant='caption' sx={{fontWeight: 'bold', ml: 1}}>{format}</Typography>
</Box>
{details && <Typography variant='caption' sx={{color: 'text.secondary'}}>{details}</Typography>}
</Box>
)
default:
return <DescriptionIcon sx={{marginRight: 1}} />
}
useEffect(() => () => {
Object.values(controllersRef.current).forEach(c => c.abort())
}, [])
const handleClick = async (e, url, fileName, fileId) => {
e.stopPropagation()
if (fileId in downloading) return
const controller = new AbortController()
controllersRef.current[fileId] = controller
setDownloading(prev => ({...prev, [fileId]: 0}))
try {
const response = await fetch(url, {signal: controller.signal})
const contentLength = +response.headers.get('content-length')
const reader = response.body.getReader()
const chunks = []
let received = 0
while (true) {
const {done, value} = await reader.read()
if (done) break
chunks.push(value)
received += value.length
if (contentLength) {
setDownloading(prev => ({...prev, [fileId]: Math.round(received / contentLength * 100)}))
}
}
const blob = new Blob(chunks, {type: response.headers.get('content-type')})
const blobUrl = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = blobUrl
a.download = fileName
a.click()
URL.revokeObjectURL(blobUrl)
} catch (error) {
if (error.name !== 'AbortError') throw error
} finally {
delete controllersRef.current[fileId]
setDownloading(prev => {
const next = {...prev}
delete next[fileId]
return next
})
}
}
const handleCancel = (e, fileId) => {
e.stopPropagation()
controllersRef.current[fileId]?.abort()
}
return (
@@ -159,10 +219,9 @@ export default function FilesList({files}) {
</ListSubheader>
<TableContainer component={Paper}>
<Table size='small' aria-label='Musiques'>
<caption><small><strong>MAX</strong> : <i>Jusqu’à 24-bit, 96 kHz, ≃ 3000 kbps (flac)</i><br /> <strong>HAUTE</strong> : <i>16 bits, ≃ 900 kbps (flac)</i></small><br /> <small><strong>FAIBLE</strong> : <i>320 kbps (ogg / aac / mp3)</i></small></caption>
<TableHead>
<TableRow>
<StyledTableCell align='center'>QUALITÉ</StyledTableCell>
<StyledTableCell align='center'>FORMAT</StyledTableCell>
<StyledTableCell />
</TableRow>
</TableHead>
@@ -170,19 +229,39 @@ export default function FilesList({files}) {
{sortedMusicFiles.map(file => (
<StyledTableRow key={file.id}>
<StyledTableCell>
{getQuality(file.ext.toLowerCase(), file?.caption?.toUpperCase())}
{renderMeta(file)}
</StyledTableCell>
<StyledTableCell align='left'>
<Link
href='#'
underline='hover'
sx={{fontWeight: 'bold'}}
sx={{fontWeight: 'bold', pointerEvents: file.id in downloading ? 'none' : 'auto'}}
aria-label='download'
onClick={e => handleClick(e, `${apiUrl}${file.url}`, file.name)}
onClick={e => handleClick(e, `${apiUrl}${file.url}`, file.name, file.id)}
>
{file.name}
</Link>
<small style={{marginLeft: 3}}>({formatSize(file.size)})</small>
{file.id in downloading && (
<Box sx={{mt: 0.5}}>
<LinearProgress
variant={downloading[file.id] > 0 ? 'determinate' : 'indeterminate'}
value={downloading[file.id]}
/>
<Box display='flex' justifyContent='space-between' alignItems='center'>
<Typography variant='caption' sx={{color: 'text.secondary'}}>
{downloading[file.id]} %
</Typography>
<Typography
variant='caption'
sx={{color: 'error.main', cursor: 'pointer'}}
onClick={e => handleCancel(e, file.id)}
>
Annuler
</Typography>
</Box>
</Box>
)}
</StyledTableCell>
</StyledTableRow>
))}
+8 -8
View File
@@ -76,7 +76,7 @@ function AjouteTradiksyon({showSwitch, disableSwitch, tradiksyonOtomatik, setTra
aria-haspopup='true'
onClick={handleClick}
>
Ajouter une traduction <br /> 🇫🇷 🇬🇧 🇪🇸 🇩🇪 🇮🇹
Ajouter une traduction <br /> 🇺🇸 🇫🇷 🇪🇸 🇩🇪 🇮🇹
</Button>
<StyledMenu
keepMounted
@@ -88,6 +88,13 @@ function AjouteTradiksyon({showSwitch, disableSwitch, tradiksyonOtomatik, setTra
}}
onClose={handleClose}
>
<StyledMenuItem
id='en'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇺🇸 Anglais (US)</StyledMenuItem>
<StyledMenuItem
id='fr'
classes={{
@@ -95,13 +102,6 @@ function AjouteTradiksyon({showSwitch, disableSwitch, tradiksyonOtomatik, setTra
}}
onClick={handleClose}
>🇫🇷 Français</StyledMenuItem>
<StyledMenuItem
id='en'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇬🇧 Anglais</StyledMenuItem>
<StyledMenuItem
id='es'
classes={{
+73 -4
View File
@@ -2,7 +2,7 @@
import {useState, useEffect, useRef} from 'react'
import PropTypes from 'prop-types'
import {styled, useTheme} from '@mui/material/styles'
import {styled, useTheme, useColorScheme} from '@mui/material/styles'
import Box from '@mui/material/Box'
import Typography from '@mui/material/Typography'
import Slider from '@mui/material/Slider'
@@ -20,6 +20,18 @@ import {getAlias} from '../../lib/utils/format'
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
function parseLrc(text) {
const lines = []
for (const raw of text.split('\n')) {
const match = raw.match(/^\[(\d{2}):(\d{2})[.:]\d+\](.*)$/)
if (match) {
lines.push({time: parseInt(match[1]) * 60 + parseInt(match[2]), text: match[3].trim()})
}
}
return lines.sort((a, b) => a.time - b.time).filter(l => l.text)
}
const Widget = styled('div')(({theme}) => ({
padding: 16,
borderRadius: 16,
@@ -59,11 +71,13 @@ export default function Lekte({audio, url, parole}) {
const audioRef = useRef(new Audio(audio))
const intervalRef = useRef()
const isReady = useRef(false)
const {duration} = audioRef.current
const [duration, setDuration] = useState(0)
const theme = useTheme()
const {mode} = useColorScheme()
const [position, setPosition] = useState(0)
const [volume, setVolume] = useState(100)
const [isPlaying, setIsPlaying] = useState(false)
const [lrcLines, setLrcLines] = useState(null)
const alias = getAlias(parole.artistes, parole.prioriteArtistes)
function formatDuration(value) {
@@ -76,8 +90,8 @@ export default function Lekte({audio, url, parole}) {
return `${minute}:${secondLeft <= 9 ? `0${secondLeft}` : secondLeft}`
}
const mainIconColor = theme.palette.mode === 'dark' ? '#fff' : '#000'
const lightIconColor = theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.4)' : grey[900]
const mainIconColor = mode === 'dark' ? '#fff' : '#000'
const lightIconColor = mode === 'dark' ? 'rgba(255,255,255,0.4)' : grey[900]
const startTimer = () => {
clearInterval(intervalRef.current)
@@ -125,8 +139,38 @@ export default function Lekte({audio, url, parole}) {
setIsPlaying(false)
setVolume(100)
setPosition(0)
setDuration(0)
}, [audio])
useEffect(() => {
const el = audioRef.current
const onLoaded = () => setDuration(Math.round(el.duration))
const onEnded = () => {
clearInterval(intervalRef.current)
setIsPlaying(false)
setPosition(0)
el.currentTime = 0
}
el.addEventListener('loadedmetadata', onLoaded)
el.addEventListener('ended', onEnded)
return () => {
el.removeEventListener('loadedmetadata', onLoaded)
el.removeEventListener('ended', onEnded)
}
}, [audio])
useEffect(() => {
if (!parole?.pawol?.url) return
fetch(new URL(parole.pawol.url, IMAGE_URL).toString())
.then(r => r.text())
.then(text => setLrcLines(parseLrc(text)))
.catch(() => setLrcLines([]))
}, [parole?.pawol?.url])
const activeIndex = lrcLines
? lrcLines.reduce((last, line, i) => line.time <= position ? i : last, -1)
: -1
const handleChangePosition = value => {
setPosition(value)
audioRef.current.currentTime = value
@@ -273,6 +317,31 @@ export default function Lekte({audio, url, parole}) {
/>
<VolumeUpRounded htmlColor={lightIconColor} />
</Stack>
{lrcLines && lrcLines.length > 0 && (
<Box sx={{mt: 1.5, textAlign: 'center', minHeight: 64}}>
{[-1, 0, 1].map(offset => {
const idx = activeIndex + offset
const line = lrcLines[idx]
if (!line) return <Box key={offset} sx={{height: offset === 0 ? 28 : 20}} />
return (
<Typography
key={offset}
variant={offset === 0 ? 'body2' : 'caption'}
sx={{
display: 'block',
fontWeight: offset === 0 ? 'bold' : 'normal',
color: offset === 0 ? 'text.primary' : 'text.secondary',
opacity: offset === 0 ? 1 : 0.45,
transition: 'all 0.3s ease',
lineHeight: offset === 0 ? 1.6 : 1.4,
}}
>
{line.text}
</Typography>
)
})}
</Box>
)}
</Widget>
</Box>
)
+4 -3
View File
@@ -40,9 +40,9 @@ const noImageUrl = 'https://place-hold.it/140x140?text=Indisponible'
export default function TeksKat({parole}) {
const router = useRouter()
const {titre, artistes, annee, couverture, publishedAt, slug} = parole
const {titre, artistes, annee, couverture, createdAt, slug} = parole
const datPiblikasyon = format(new Date(publishedAt), 'P', {locale: fr})
const datPiblikasyon = format(new Date(createdAt), 'P', {locale: fr})
const aliases = getAlias(artistes, parole.prioriteArtistes)
const handleClick = slug => {
@@ -57,8 +57,9 @@ export default function TeksKat({parole}) {
className={classes.media}
component='img'
alt={titre}
image={couverture?.url ? `${IMAGE_URL}${couverture.url}` : noImageUrl}
image={couverture?.url ? `${IMAGE_URL}${couverture.formats?.thumbnail?.url || couverture.url}` : noImageUrl}
title={titre}
loading='lazy'
/>
<CardContent>
<Box sx={{display: 'flex', alignItems: 'center'}}>
+22 -5
View File
@@ -13,7 +13,10 @@ import slugify from 'slugify'
import {styled} from '@mui/material/styles'
import ExplicitIcon from '@mui/icons-material/Explicit'
import Image from 'next/image'
import {formatJsonString, getAlias} from '../../lib/utils/format'
import {formatKuveti} from '../../lib/kuveti'
import LicenseModal from '../cc/license-modal'
import FilesDialog from '../files/files-dialog'
@@ -21,6 +24,8 @@ import EntegreMizik from './entegre-mizik'
import OkiMizik from './oki-mizik'
import DiferansDialog from './diferans-dialog'
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
const PREFIX = 'teks'
const classes = {
@@ -78,14 +83,14 @@ const langToArray = parole => {
if (parole && parole.traductions) {
const {francais, anglais, espagnol, allemand, italien} = parole.traductions
if (francais) {
langArray.push({title: 'Traduction', flag: 'fr', lang: francais})
}
if (anglais) {
langArray.push({title: 'Translation', flag: 'en', lang: anglais})
}
if (francais) {
langArray.push({title: 'Traduction', flag: 'fr', lang: francais})
}
if (espagnol) {
langArray.push({title: 'Traducción', flag: 'es', lang: espagnol})
}
@@ -126,6 +131,7 @@ export default function Teks({parole}) {
const isMobile = useMediaQuery('(max-width:600px)')
const langArray = langToArray(parole)
const enhancedAliases = getAlias(parole.artistes, parole.prioriteArtistes, true)
const coverFmt = formatKuveti(parole.couverture)
useEffect(() => {
const isBrowser = () => typeof window !== 'undefined'
@@ -169,6 +175,17 @@ export default function Teks({parole}) {
</Box>
</Typography>
{coverFmt?.url && (
<Box sx={{display: 'flex', justifyContent: 'center', mb: 2}}>
<Image
src={new URL(coverFmt.url, IMAGE_URL).toString()}
alt={parole.titre}
width={coverFmt.width || 300}
height={coverFmt.height || 300}
style={{maxWidth: '100%', maxHeight: 320, width: 'auto', height: 'auto', borderRadius: 8}}
/>
</Box>
)}
{parole?.user && (
<Typography style={{marginBottom: '1.5em'}} display='block' variant='caption'>
<i>parole soumise par {parole.user.username}</i>
@@ -221,7 +238,7 @@ export default function Teks({parole}) {
)}
{flag === 'en' && (
<span>
🇬🇧
🇺🇸
</span>
)}
{flag === 'es' && (
+9
View File
@@ -0,0 +1,9 @@
import {SvgIcon} from '@mui/material'
export default function GiteaIcon(props) {
return (
<SvgIcon {...props}>
<path d='M4.186 5.421C2.341 5.417-.13 6.59.006 9.531c.213 4.594 4.92 5.02 6.801 5.057.206.862 2.42 3.834 4.059 3.99h7.18c4.306-.286 7.53-13.022 5.14-13.07-3.953.186-6.296.28-8.305.296v3.975l-.626-.277-.004-3.696c-2.306-.001-4.336-.108-8.189-.298-.482-.003-1.154-.085-1.876-.087zm.261 1.625h.22c.262 2.355.688 3.732 1.55 5.836-2.2-.26-4.072-.899-4.416-3.285-.178-1.235.422-2.524 2.646-2.552zm8.557 2.315c.15.002.303.03.447.096l.749.323-.537.979a.672.597 0 0 0-.241.038.672.597 0 0 0-.405.764.672.597 0 0 0 .112.174l-.926 1.686a.672.597 0 0 0-.222.038.672.597 0 0 0-.405.764.672.597 0 0 0 .86.36.672.597 0 0 0 .404-.765.672.597 0 0 0-.158-.22l.902-1.642a.672.597 0 0 0 .293-.03.672.597 0 0 0 .213-.112c.348.146.633.265.838.366.308.152.417.253.45.365.033.11-.003.322-.177.694-.13.277-.345.67-.599 1.133a.672.597 0 0 0-.251.038.672.597 0 0 0-.405.764.672.597 0 0 0 .86.36.672.597 0 0 0 .404-.764.672.597 0 0 0-.137-.202c.251-.458.467-.852.606-1.148.188-.402.286-.701.2-.99-.086-.289-.35-.477-.7-.65-.23-.113-.517-.233-.86-.377a.672.597 0 0 0-.038-.239.672.597 0 0 0-.145-.209l.528-.963 2.924 1.263c.528.229.746.79.49 1.26l-2.01 3.68c-.257.469-.888.663-1.416.435l-4.137-1.788c-.528-.228-.747-.79-.49-1.26l2.01-3.679c.176-.323.53-.515.905-.53h.064z' />
</SvgIcon>
)
}
+3
View File
@@ -56,6 +56,9 @@ export async function jwennTeksEpiSlug(slug) {
},
traductions: {
populate: '*'
},
pawol: {
populate: '*'
}
},
filters: {
+7 -17
View File
@@ -4,10 +4,10 @@ import TelegramIcon from '@mui/icons-material/Telegram'
import PeertubeIcon from './icons/peertube'
import XmppIcon from './icons/xmpp'
import XIcon from './icons/x'
import CodebergIcon from './icons/codeberg'
import GiteaIcon from './icons/gitea'
import BlueskyIcon from './icons/bluesky'
const codebergURL = process.env.NEXT_PUBLIC_CODEBERG || 'https://codeberg.org/OKI'
const gitURL = process.env.NEXT_PUBLIC_GIT || 'https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/pawol.nu'
const blueskyUrl = process.env.NEXT_PUBLIC_BLUESKY_URL || 'https://bsky.app/profile/organisationka.bsky.social'
const xmppContact = process.env.NEXT_PUBLIC_XMPP || 'oki@xmpp.cz'
const gadeUsername = process.env.NEXT_PUBLIC_GADE_USERNAME || 'oki'
@@ -17,25 +17,15 @@ const telegramGroup = process.env.NEXT_PUBLIC_TELEGRAM_GROUP || 'OrganisationKA'
export const rezoLis = [
{
tit: 'Codeberg',
lyen: codebergURL,
icon: <CodebergIcon />
tit: 'Git',
lyen: gitURL,
icon: <GiteaIcon />
},
{
tit: 'Bluesky',
lyen: blueskyUrl,
icon: <BlueskyIcon />
},
{
tit: 'XMPP',
lyen: `xmpp:${xmppContact}`,
icon: <XmppIcon />
},
{
tit: 'Gadé',
lyen: `https://gade.o-k-i.net/a/${gadeUsername}/video-channels`,
icon: <PeertubeIcon />
},
{
tit: 'Telegram',
lyen: `https://t.me/${telegramGroup}`,
@@ -47,8 +37,8 @@ export const rezoLis = [
icon: <YouTubeIcon />
},
{
tit: 'Twitter',
lyen: `https://twitter.com/${tiwtterUsername}`,
tit: 'X',
lyen: `https://x.com/${tiwtterUsername}`,
icon: <XIcon />
}
]
+6 -4
View File
@@ -5,13 +5,13 @@
"private": false,
"license": "AGPL-3.0",
"author": {
"name": "Cédric Famibelle-Pronzola",
"email": "contact@cedric-pronzola.dev",
"url": "https://cedric-pronzola.dev"
"name": "ORGANSATION KA INTERNATIONALE",
"email": "kontak@o-k-i.net",
"url": "https://o-k-i.net"
},
"repository": {
"type": "git",
"url": "git+https://codeberg.org/OKI/pawol.nu.git"
"url": "git+https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/api.pawol.nu"
},
"scripts": {
"lint": "xo",
@@ -40,8 +40,10 @@
"express": "^4.17.1",
"file-saver": "^2.0.5",
"lodash": "^4.17.21",
"music-metadata-browser": "^2.5.11",
"next": "16.2.4",
"next-auth": "^5.0.0-beta.31",
"next-plausible": "^4.0.0",
"next-pwa": "5.6.0",
"nextjs-toploader": "^3.9.17",
"nodemailer": "^6.7.2",
+124 -2
View File
@@ -3212,6 +3212,11 @@
dependencies:
tslib "^2.8.0"
"@tokenizer/token@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==
"@trysound/sax@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
@@ -3618,6 +3623,13 @@
stylis "^4.3.0"
ts-invariant "^0.10.3"
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
accepts@~1.3.5, accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -4184,6 +4196,14 @@ buffer@^5.5.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
builtin-modules@^3.0.0, builtin-modules@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
@@ -4449,6 +4469,11 @@ content-disposition@0.5.3:
dependencies:
safe-buffer "5.1.2"
content-type@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
@@ -5708,6 +5733,16 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
events@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
execa@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
@@ -5841,6 +5876,15 @@ file-saver@^2.0.5:
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
file-type@^16.5.4:
version "16.5.4"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd"
integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==
dependencies:
readable-web-to-node-stream "^3.0.0"
strtok3 "^6.2.4"
token-types "^4.1.1"
filelist@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
@@ -6427,7 +6471,7 @@ idb@^7.0.1:
resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.0.tgz#2cc886be57738419e57f9aab58f647e5e2160270"
integrity sha512-Wsk07aAxDsntgYJY4h0knZJuTxM73eQ4reRAO+Z1liOh8eMCJ/MoDS8fCui1vGT9mnjtl1sOu3I2i/W1swPYZg==
ieee754@^1.1.13:
ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@@ -7479,6 +7523,11 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
media-typer@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561"
integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==
memory-fs@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290"
@@ -7657,6 +7706,30 @@ multipipe@^1.0.2:
duplexer2 "^0.1.2"
object-assign "^4.1.0"
music-metadata-browser@^2.5.11:
version "2.5.11"
resolved "https://registry.yarnpkg.com/music-metadata-browser/-/music-metadata-browser-2.5.11.tgz#dd28bc6506075ac46ce33f72e6828742b4b6cb9e"
integrity sha512-Khq5nYapffIet0PUVb5J69pZPgqgn+/yoEr0jkO/OjH5xwfdz6rdwj0zsWPaqo3ylv+OthXoGjT6EegVHbMkJQ==
dependencies:
buffer "^6.0.3"
debug "^4.3.4"
music-metadata "^7.13.3"
readable-stream "^4.3.0"
readable-web-to-node-stream "^3.0.2"
music-metadata@^7.13.3:
version "7.14.0"
resolved "https://registry.yarnpkg.com/music-metadata/-/music-metadata-7.14.0.tgz#74e3e5fc8e09b86d1a3e791fb5ce9ccdc4347ad9"
integrity sha512-xrm3w7SV0Wk+OythZcSbaI8mcr/KHd0knJieu8bVpaPfMv/Agz5EooCAPz3OR5hbYMiUG6dgAPKZKnMzV+3amA==
dependencies:
"@tokenizer/token" "^0.3.0"
content-type "^1.0.5"
debug "^4.3.4"
file-type "^16.5.4"
media-typer "^1.1.0"
strtok3 "^6.3.0"
token-types "^4.2.1"
nanoid@^3.3.6:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
@@ -7689,6 +7762,11 @@ next-auth@^5.0.0-beta.31:
dependencies:
"@auth/core" "0.41.2"
next-plausible@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/next-plausible/-/next-plausible-4.0.0.tgz#bc81be4f2c9ba4783f07b54d5fed5d4650c265aa"
integrity sha512-tC48VscREZ4fEvas9T4oj5qJwnpPlms0Wih1Unbgi/ozG08yN1w0IAPGp+/cHB8n6qzEAL5J0MlAS0FOr132jA==
next-pwa@5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/next-pwa/-/next-pwa-5.6.0.tgz#f7b1960c4fdd7be4253eb9b41b612ac773392bf4"
@@ -8184,6 +8262,11 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
peek-readable@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72"
integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
@@ -8327,6 +8410,11 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
@@ -8555,6 +8643,17 @@ readable-stream@^3.1.1, readable-stream@^3.4.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@^4.3.0, readable-stream@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91"
integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==
dependencies:
abort-controller "^3.0.0"
buffer "^6.0.3"
events "^3.3.0"
process "^0.11.10"
string_decoder "^1.3.0"
readable-stream@~1.0.17, readable-stream@~1.0.27-1:
version "1.0.34"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
@@ -8565,6 +8664,13 @@ readable-stream@~1.0.17, readable-stream@~1.0.27-1:
isarray "0.0.1"
string_decoder "~0.10.x"
readable-web-to-node-stream@^3.0.0, readable-web-to-node-stream@^3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz#392ba37707af5bf62d725c36c1b5d6ef4119eefc"
integrity sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==
dependencies:
readable-stream "^4.7.0"
redent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9"
@@ -9397,7 +9503,7 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
string_decoder@^1.1.1:
string_decoder@^1.1.1, string_decoder@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
@@ -9485,6 +9591,14 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
strtok3@^6.2.4, strtok3@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0"
integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==
dependencies:
"@tokenizer/token" "^0.3.0"
peek-readable "^4.1.0"
styled-jsx@5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.6.tgz#83b90c077e6c6a80f7f5e8781d0f311b2fe41499"
@@ -9688,6 +9802,14 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
token-types@^4.1.1, token-types@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753"
integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==
dependencies:
"@tokenizer/token" "^0.3.0"
ieee754 "^1.2.1"
tr46@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"