Compare commits
64 Commits
dev
...
e3366dd821
| Author | SHA1 | Date | |
|---|---|---|---|
|
e3366dd821
|
|||
|
475cbe9028
|
|||
| 288d771075 | |||
|
cd5ebb8058
|
|||
|
aa3032d25e
|
|||
|
cd24d90b67
|
|||
|
5c16f126a7
|
|||
|
69535aa3f4
|
|||
|
ce8053a3f6
|
|||
|
9cbb5e3d23
|
|||
|
f2d03ebec6
|
|||
|
2232cd7360
|
|||
|
0040902151
|
|||
|
3133145fd9
|
|||
|
35e1a3a010
|
|||
|
9949efd0c1
|
|||
|
972e41d528
|
|||
|
2d83b878fe
|
|||
|
eba16e7ec8
|
|||
|
6121a2ca4a
|
|||
|
5a6273e9e3
|
|||
|
ac24abb051
|
|||
|
9d1c51337c
|
|||
|
4d751482c2
|
|||
|
45d1891df6
|
|||
|
e7bf523f75
|
|||
|
d74691b2a5
|
|||
|
0c339e7201
|
|||
|
b12952f85e
|
|||
|
f28fa96f89
|
|||
|
ed819e3537
|
|||
|
a89196909e
|
|||
|
d80a77d9e7
|
|||
|
2f46b6372f
|
|||
|
ad496f50b0
|
|||
|
17ef7f5534
|
|||
|
9c18e52046
|
|||
|
0b50c9ac2f
|
|||
|
d2b779f8de
|
|||
|
9adc045a98
|
|||
|
f500b2eaa1
|
|||
|
b018c2a917
|
|||
|
ca9ba4cb10
|
|||
|
c113a2547f
|
|||
|
6b54f13b3f
|
|||
|
486a852195
|
|||
|
a51744e941
|
|||
|
13d60a1b32
|
|||
|
4955327334
|
|||
|
57eeffc8f7
|
|||
|
a09b836218
|
|||
|
38deb84be8
|
|||
|
2dc0e7933c
|
|||
|
0e2bcb17b2
|
|||
|
cd3cc2a697
|
|||
|
7161070435
|
|||
|
cb37979fc4
|
|||
|
9f8e60d56f
|
|||
|
d02d946cfb
|
|||
|
eef02431b8
|
|||
|
e05c61f0fe
|
|||
|
67aa60a53b
|
|||
|
860bfe74df
|
|||
| 921318f92f |
+25
-2
@@ -6,6 +6,16 @@ SITE_URL=http://localhost:3001
|
||||
NEXT_PUBLIC_READ_TOKEN=
|
||||
NEXT_PUBLIC_ADMIN_JWT_SECRET=
|
||||
|
||||
# IDENTITÉ DU SITE (branding)
|
||||
NEXT_PUBLIC_SITE_NAME=PAWÒL-NU. Paroles et traductions.
|
||||
NEXT_PUBLIC_SITE_SHORT_NAME=PAWÒL-NU
|
||||
NEXT_PUBLIC_SITE_DESCRIPTION=PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.
|
||||
NEXT_PUBLIC_ORG_NAME=OKI
|
||||
NEXT_PUBLIC_ORG_FULL_NAME=ORGANISATION KA INTERNATIONALE
|
||||
NEXT_PUBLIC_ORG_EMAIL=kontak@o-k-i.net
|
||||
NEXT_PUBLIC_ORG_LOCATION=Guadeloupe
|
||||
NEXT_PUBLIC_EXCLUSIVE_ARTIST_LABEL=OKI Exclusif
|
||||
|
||||
# FUNKWHALE VARIABLE
|
||||
NEXT_PUBLIC_OKI_MIZIK_URL=https://funkwhale-server.com
|
||||
NEXT_PUBLIC_MIZIK_API_USER=user
|
||||
@@ -14,9 +24,11 @@ NEXT_PUBLIC_MIZIK_API_PASSWORD=password
|
||||
NEXT_PUBLIC_AWTIS_POU_CHAK_PAJ=6
|
||||
NEXT_PUBLIC_SITE_URL=$SITE_URL
|
||||
|
||||
# NEXT AUTH
|
||||
# Auth
|
||||
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
BETTER_AUTH_URL=http://localhost:3000
|
||||
BETTER_AUTH_SECRET=
|
||||
|
||||
# TWITTER VARIABLE
|
||||
|
||||
@@ -30,7 +42,13 @@ GOOGLE_CLIENT_SECRET=
|
||||
|
||||
# CGU
|
||||
|
||||
NEXT_PUBLIC_CGU_DOWNLOAD_LINK=
|
||||
NEXT_PUBLIC_CGU_DOWNLOAD_LINK=/cgu/cgu-confidentialite-oki-26-06-2026.pdf
|
||||
|
||||
# Debug (mettre YES pour afficher les emails dans les logs en développement)
|
||||
SHOW_EMAILS=
|
||||
|
||||
# Analytique (Plausible — laisser vide pour désactiver)
|
||||
NEXT_PUBLIC_PLAUSIBLE_URL=
|
||||
|
||||
# Menu
|
||||
|
||||
@@ -61,7 +79,9 @@ NEXT_PUBLIC_GADE_USERNAME=
|
||||
NEXT_PUBLIC_YOUTUBE_USERNAME=
|
||||
NEXT_PUBLIC_TELEGRAM_GROUP=
|
||||
NEXT_PUBLIC_XMPP=
|
||||
NEXT_PUBLIC_GIT=
|
||||
NEXT_PUBLIC_CODEBERG=
|
||||
NEXT_PUBLIC_BLUESKY_URL=
|
||||
|
||||
# DOMAIN IMAGE
|
||||
NEXT_PUBLIC_DOMAINS_IMAGE="localhost:1337 strapi.mondomaine.com"
|
||||
@@ -85,8 +105,11 @@ SMTP_PASSWORD=
|
||||
SMTP_FROM=
|
||||
SMTP_REPLY_TO=
|
||||
SMTP_SEND_TO=
|
||||
SMTP_BCC=
|
||||
SMTP_SECURE=
|
||||
|
||||
# Twitter Authentification
|
||||
|
||||
NEXT_PUBLIC_TWITTER_API_KEY=
|
||||
NEXT_PUBLIC_TWITTER_API_KEY_SECRET=
|
||||
NEXT_PUBLIC_TWITTER_BEARER_TOKEN=
|
||||
|
||||
@@ -2,8 +2,6 @@ name: Déploiement FRONT BETA
|
||||
run-name: ${{ gitea.actor }} déploie FRONT BETA
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
check:
|
||||
@@ -36,7 +34,8 @@ jobs:
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
cd ${{ secrets.FRONT_DEPLOY_PATH }}
|
||||
git pull --ff-only origin dev
|
||||
git fetch origin
|
||||
git checkout -B ${{ gitea.ref_name }} origin/${{ gitea.ref_name }}
|
||||
corepack enable
|
||||
yarn install --frozen-lockfile
|
||||
yarn build
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -50,11 +50,11 @@ export async function generateMetadata(props) {
|
||||
type: 'website'
|
||||
},
|
||||
twitter: {
|
||||
site: '@OrganisationKA',
|
||||
site: `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`,
|
||||
card: 'summary_large_image',
|
||||
title,
|
||||
description,
|
||||
creator: '@OrganisationKA',
|
||||
creator: `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`,
|
||||
images: {
|
||||
url: `${apiUrl}${kuvetiFormat?.url}`,
|
||||
alt: `Photo de ${anAwtis.alias}`,
|
||||
|
||||
+14
-10
@@ -10,17 +10,21 @@ import Pajinasyon from '../../components/awtis/pajinasyon'
|
||||
import {jwennAwtisPajinasyon} from '../../lib/oki-api'
|
||||
import Footer from '../../components/footer'
|
||||
|
||||
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || 'PAWÒL-NU. Paroles et traductions.'
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pawol.nu'
|
||||
const twitterHandle = `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`
|
||||
|
||||
export const metadata = {
|
||||
title: 'OKI | Awtis - Liste des artistes',
|
||||
title: `${siteName} | Artistes`,
|
||||
description: 'Liste des artistes ayant une ou plusieurs œuvres présentes sur le site.',
|
||||
openGraph: {
|
||||
title: 'OKI | Awtis - Liste des artistes',
|
||||
title: `${siteName} | 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.',
|
||||
url: `${siteUrl}/awtis`,
|
||||
siteName,
|
||||
images: [
|
||||
{
|
||||
url: 'https://pawol.nu/logo-512x512.png',
|
||||
url: `${siteUrl}/logo-512x512.png`,
|
||||
width: 512,
|
||||
height: 512
|
||||
}
|
||||
@@ -29,14 +33,14 @@ export const metadata = {
|
||||
type: 'website'
|
||||
},
|
||||
twitter: {
|
||||
site: '@OrganisationKA',
|
||||
site: twitterHandle,
|
||||
card: 'summary_large_image',
|
||||
title: 'OKI | Awtis - Liste des artistes',
|
||||
title: `${siteName} | Artistes`,
|
||||
description: 'Liste des artistes ayant une ou plusieurs œuvres présentes sur le site.',
|
||||
creator: '@OrganisationKA',
|
||||
creator: twitterHandle,
|
||||
images: {
|
||||
url: 'https://pawol.nu/logo-512x512.png',
|
||||
alt: 'OKI Logo',
|
||||
url: `${siteUrl}/logo-512x512.png`,
|
||||
alt: `${siteName} Logo`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+54
-36
@@ -1,25 +1,35 @@
|
||||
import PlausibleProvider from 'next-plausible'
|
||||
import TopLoader from '../components/top-loader'
|
||||
import Navigasyon from '../components/navigasyon'
|
||||
import ThemeRegistry from './theme-registy'
|
||||
|
||||
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || 'PAWÒL-NU. Paroles et traductions.'
|
||||
const siteDescription = process.env.NEXT_PUBLIC_SITE_DESCRIPTION || 'PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.'
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pawol.nu'
|
||||
const orgName = process.env.NEXT_PUBLIC_ORG_NAME || 'OKI'
|
||||
const orgEmail = process.env.NEXT_PUBLIC_ORG_EMAIL || 'kontak@o-k-i.net'
|
||||
const orgLocation = process.env.NEXT_PUBLIC_ORG_LOCATION || 'Guadeloupe'
|
||||
const twitterHandle = `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`
|
||||
const plausibleUrl = process.env.NEXT_PUBLIC_PLAUSIBLE_URL || null
|
||||
|
||||
export const metadata = {
|
||||
metadataBase: new URL('https://pawol.nu'),
|
||||
manifest: '/manifest.json',
|
||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
||||
description: 'PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.',
|
||||
author: 'OKI',
|
||||
metadataBase: new URL(siteUrl),
|
||||
manifest: '/manifest.webmanifest',
|
||||
title: siteName,
|
||||
description: siteDescription,
|
||||
author: orgName,
|
||||
category: 'music',
|
||||
creator: 'OKI',
|
||||
publisher: 'OKI',
|
||||
applicationName: 'PAWÒL-NU. Paroles et traductions.',
|
||||
creator: orgName,
|
||||
publisher: orgName,
|
||||
applicationName: siteName,
|
||||
openGraph: {
|
||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
||||
description: 'PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.',
|
||||
url: 'https://pawol.nu',
|
||||
siteName: 'PAWÒL-NU. Paroles et traductions.',
|
||||
title: siteName,
|
||||
description: siteDescription,
|
||||
url: siteUrl,
|
||||
siteName,
|
||||
images: [
|
||||
{
|
||||
url: 'https://pawol.nu/logo-512x512.png',
|
||||
url: `${siteUrl}/logo-512x512.png`,
|
||||
width: 512,
|
||||
height: 512
|
||||
}
|
||||
@@ -28,14 +38,14 @@ export const metadata = {
|
||||
type: 'website'
|
||||
},
|
||||
twitter: {
|
||||
site: '@OrganisationKA',
|
||||
site: twitterHandle,
|
||||
card: 'summary_large_image',
|
||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
||||
description: 'PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.',
|
||||
creator: '@OrganisationKA',
|
||||
title: siteName,
|
||||
description: siteDescription,
|
||||
creator: twitterHandle,
|
||||
images: {
|
||||
url: 'https://pawol.nu/logo-512x512.png',
|
||||
alt: 'PAWÒL-NU Logo',
|
||||
url: `${siteUrl}/logo-512x512.png`,
|
||||
alt: `${siteName} Logo`,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -43,28 +53,36 @@ export const metadata = {
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
url: 'https://pawol.nu',
|
||||
email: 'kontak@o-k-i.net',
|
||||
keywords: ['OKI', 'PAWÒL-NU', 'Paroles', 'Pawol', 'Medukam', 'Wanni Wannan'],
|
||||
legalName: 'PAWÒL-NU',
|
||||
location: 'Guadeloupe'
|
||||
url: siteUrl,
|
||||
email: orgEmail,
|
||||
keywords: [orgName, siteName, 'Paroles', 'Pawol'],
|
||||
legalName: siteName,
|
||||
location: orgLocation,
|
||||
}
|
||||
|
||||
export default async function RootLayout({children, Session}) {
|
||||
export default async function RootLayout({children}) {
|
||||
const inner = (
|
||||
<>
|
||||
<TopLoader color='#ffeb3b' />
|
||||
<ThemeRegistry>
|
||||
<Navigasyon />
|
||||
{children}
|
||||
</ThemeRegistry>
|
||||
<section>
|
||||
<script
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{__html: JSON.stringify(jsonLd)}}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<html lang='fr' suppressHydrationWarning>
|
||||
<body>
|
||||
<TopLoader color='#ffeb3b' />
|
||||
<ThemeRegistry>
|
||||
<Navigasyon />
|
||||
{children}
|
||||
</ThemeRegistry>
|
||||
<section>
|
||||
<script
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{__html: JSON.stringify(jsonLd)}}
|
||||
/>
|
||||
</section>
|
||||
{plausibleUrl
|
||||
? <PlausibleProvider src={plausibleUrl}>{inner}</PlausibleProvider>
|
||||
: inner}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
export default function manifest() {
|
||||
return {
|
||||
name: process.env.NEXT_PUBLIC_SITE_NAME || 'PAWÒL-NU',
|
||||
short_name: process.env.NEXT_PUBLIC_SITE_SHORT_NAME || 'PAWÒL-NU',
|
||||
description: process.env.NEXT_PUBLIC_SITE_DESCRIPTION || 'PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.',
|
||||
scope: '/',
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#303030',
|
||||
theme_color: '#303030',
|
||||
orientation: 'portrait-primary',
|
||||
icons: [
|
||||
{src: '/logo-72x72.png', type: 'image/png', sizes: '72x72'},
|
||||
{src: '/logo-96x96.png', type: 'image/png', sizes: '96x96'},
|
||||
{src: '/logo-128x128.png', type: 'image/png', sizes: '128x128'},
|
||||
{src: '/logo-144x144.png', type: 'image/png', sizes: '144x144'},
|
||||
{src: '/logo-152x152.png', type: 'image/png', sizes: '152x152'},
|
||||
{src: '/logo-192x192.png', type: 'image/png', sizes: '192x192'},
|
||||
{src: '/logo-256x256.png', type: 'image/png', sizes: '256x256'},
|
||||
{src: '/logo-384x384.png', type: 'image/png', sizes: '384x384'},
|
||||
{src: '/logo-512x512.png', type: 'image/png', sizes: '512x512'},
|
||||
{src: '/maskable.png', type: 'image/png', sizes: '192x192', purpose: 'maskable'},
|
||||
],
|
||||
}
|
||||
}
|
||||
+8
-4
@@ -1,32 +1,36 @@
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
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, jwennAnVedette} 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, anVedette] = await Promise.all([jwennStats(), jwennDenyeTeks(), jwennAnVedette()])
|
||||
|
||||
if (!statistik) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return statistik
|
||||
return {statistik, dernierTeks: anVedette ?? 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>
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -53,11 +53,11 @@ export async function generateMetadata(props) {
|
||||
type: 'website'
|
||||
},
|
||||
twitter: {
|
||||
site: '@OrganisationKA',
|
||||
site: `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`,
|
||||
card: 'summary_large_image',
|
||||
title,
|
||||
description,
|
||||
creator: '@OrganisationKA',
|
||||
creator: `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`,
|
||||
images: {
|
||||
url: `${apiUrl}${kuvetiFormat?.url}`,
|
||||
alt: `Couverture ${title}`,
|
||||
@@ -80,8 +80,12 @@ export default async function AnPawolPaj(props) {
|
||||
'@id': anTeks.musicBrainzUrl || undefined,
|
||||
name: anTeks.titre,
|
||||
url: `${siteUrl}/paroles/${slug}`,
|
||||
inLanguage: anTeks.langueSource ?? 'ka',
|
||||
image: teksKuvetiFormat?.url ? `${apiUrl}${teksKuvetiFormat?.url}` : undefined,
|
||||
thumbnailUrl: couverture?.formats?.thumbnail?.url ? `${apiUrl}${couverture.formats.thumbnail.url}` : undefined,
|
||||
license: anTeks.creativeCommons
|
||||
? `https://creativecommons.org/licenses/${anTeks.creativeCommons.toLowerCase()}/4.0/`
|
||||
: undefined,
|
||||
byArtist: anTeks.artistes.map(({photo, musicBrainzUrl, alias, slug}) => {
|
||||
const kuvetiFormat = formatKuveti(photo)
|
||||
|
||||
@@ -93,7 +97,18 @@ export default async function AnPawolPaj(props) {
|
||||
image: kuvetiFormat?.url ? `${apiUrl}${kuvetiFormat?.url}` : undefined
|
||||
}
|
||||
}),
|
||||
datePublished: anTeks?.annee
|
||||
datePublished: anTeks?.annee,
|
||||
potentialAction: anTeks.streamAudio?.length > 0
|
||||
? anTeks.streamAudio.map(lyen => ({
|
||||
'@type': 'ListenAction',
|
||||
target: lyen.url
|
||||
}))
|
||||
: undefined,
|
||||
isBasedOn: anTeks.sourceOriginale ? {
|
||||
'@type': 'MusicRecording',
|
||||
name: anTeks.sourceOriginale.titre,
|
||||
url: `${siteUrl}/paroles/${anTeks.sourceOriginale.slug}`
|
||||
} : undefined
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+14
-10
@@ -4,17 +4,21 @@ import {jwennTeks} from '../../lib/oki-api'
|
||||
import TeksDrawer from '../../components/teks/teks-drawer'
|
||||
import Loading from './loading'
|
||||
|
||||
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || 'PAWÒL-NU. Paroles et traductions.'
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pawol.nu'
|
||||
const twitterHandle = `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`
|
||||
|
||||
export const metadata = {
|
||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
||||
title: siteName,
|
||||
description: 'Retrouvez les paroles et les traductions de vos chansons préférées.',
|
||||
openGraph: {
|
||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
||||
title: siteName,
|
||||
description: 'Retrouvez les paroles et les traductions de vos chansons préférées.',
|
||||
url: 'https://pawol.nu/paroles',
|
||||
siteName: 'PAWÒL-NU. Paroles et traductions.',
|
||||
url: `${siteUrl}/paroles`,
|
||||
siteName,
|
||||
images: [
|
||||
{
|
||||
url: 'https://pawol.nu/logo-512x512.png',
|
||||
url: `${siteUrl}/logo-512x512.png`,
|
||||
width: 512,
|
||||
height: 512
|
||||
}
|
||||
@@ -23,14 +27,14 @@ export const metadata = {
|
||||
type: 'website'
|
||||
},
|
||||
twitter: {
|
||||
site: '@OrganisationKA',
|
||||
site: twitterHandle,
|
||||
card: 'summary_large_image',
|
||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
||||
title: siteName,
|
||||
description: 'Retrouvez les paroles et les traductions de vos chansons préférées.',
|
||||
creator: '@OrganisationKA',
|
||||
creator: twitterHandle,
|
||||
images: {
|
||||
url: 'https://pawol.nu/logo-512x512.png',
|
||||
alt: 'OKI Logo',
|
||||
url: `${siteUrl}/logo-512x512.png`,
|
||||
alt: `${siteName} Logo`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,15 @@ 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'
|
||||
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || 'PAWÒL-NU. Paroles et traductions.'
|
||||
const twitterHandle = `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`
|
||||
|
||||
async function jwennDone() {
|
||||
const denyeTeks = await jwennDenyeTeks()
|
||||
|
||||
@@ -15,6 +21,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: `${siteName} | Derniers morceaux`,
|
||||
description,
|
||||
openGraph: {
|
||||
title: `${siteName} | Derniers morceaux`,
|
||||
description,
|
||||
url: `${siteUrl}/paroles`,
|
||||
siteName,
|
||||
images: [{url: imageUrl, width: imageWidth, height: imageHeight}],
|
||||
locale: 'fr_FR',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
site: twitterHandle,
|
||||
card: 'summary_large_image',
|
||||
title: `${siteName} | Derniers morceaux`,
|
||||
description,
|
||||
creator: twitterHandle,
|
||||
images: {url: imageUrl, alt: 'Couverture du dernier morceau publié'},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function PawolPaj() {
|
||||
const denyeTeks = await jwennDone()
|
||||
|
||||
|
||||
+1
-1
@@ -15,6 +15,6 @@ export default function robots() {
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
},
|
||||
sitemap: 'https://pawol.nu/sitemap.xml',
|
||||
sitemap: `${process.env.NEXT_PUBLIC_SITE_URL || 'https://pawol.nu'}/sitemap.xml`,
|
||||
}
|
||||
}
|
||||
|
||||
+16
-10
@@ -1,14 +1,20 @@
|
||||
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || 'PAWÒL-NU. Paroles et traductions.'
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pawol.nu'
|
||||
const orgFullName = process.env.NEXT_PUBLIC_ORG_FULL_NAME || process.env.NEXT_PUBLIC_ORG_NAME || 'ORGANISATION KA INTERNATIONALE'
|
||||
const twitterHandle = `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`
|
||||
const sipoteTitle = `${siteName} | Soutenir ${orgFullName} !`
|
||||
|
||||
export const metadata = {
|
||||
title: 'PAWÒL-NU | Soutenir ORGANISATION KA INTERNATIONALE !',
|
||||
title: sipoteTitle,
|
||||
description: 'Vous pouvez nous soutenir via Liberapay ou PayPal',
|
||||
openGraph: {
|
||||
title: 'PAWÒL-NU | Soutenir ORGANISATION KA INTERNATIONALE !',
|
||||
title: sipoteTitle,
|
||||
description: 'Vous pouvez nous soutenir via Liberapay ou PayPal.',
|
||||
url: 'https://pawol.nu/sipote',
|
||||
siteName: 'PAWÒL-NU | Paroles et traductions.',
|
||||
url: `${siteUrl}/sipote`,
|
||||
siteName,
|
||||
images: [
|
||||
{
|
||||
url: 'https://pawol.nu/sipote.png',
|
||||
url: `${siteUrl}/sipote.png`,
|
||||
width: 500,
|
||||
height: 500
|
||||
}
|
||||
@@ -17,14 +23,14 @@ export const metadata = {
|
||||
type: 'website'
|
||||
},
|
||||
twitter: {
|
||||
site: '@OrganisationKA',
|
||||
site: twitterHandle,
|
||||
card: 'summary_large_image',
|
||||
title: 'PAWÒL-NU | Soutenir ORGANISATION KA INTERNATIONALE !',
|
||||
title: sipoteTitle,
|
||||
description: 'Vous pouvez nous soutenir via Liberapay ou PayPal.',
|
||||
creator: '@OrganisationKA',
|
||||
creator: twitterHandle,
|
||||
images: {
|
||||
url: 'https://pawol.nu/sipote.png',
|
||||
alt: 'Sipòte OKI',
|
||||
url: `${siteUrl}/sipote.png`,
|
||||
alt: `Sipòte ${process.env.NEXT_PUBLIC_ORG_NAME || 'OKI'}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
'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 Typography from '@mui/material/Typography'
|
||||
import Box from '@mui/material/Box'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Image from 'next/image'
|
||||
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'
|
||||
|
||||
// 1×1 gris neutre — placeholder pendant le chargement
|
||||
const BLUR_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNsYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
|
||||
|
||||
export default function AnVedette({teks}) {
|
||||
const {titre, artistes, annee, couverture, slug} = teks
|
||||
const aliases = getAlias(artistes, teks.prioriteArtistes)
|
||||
const fmt = formatKuveti(couverture, 'medium')
|
||||
|
||||
return (
|
||||
<Box sx={{mb: 4}}>
|
||||
<Chip
|
||||
label='NEW RELEASE'
|
||||
size='small'
|
||||
color='primary'
|
||||
sx={{mb: 1.5, fontWeight: 'bold', letterSpacing: 1}}
|
||||
/>
|
||||
<Card sx={{borderRadius: 2, overflow: 'hidden'}}>
|
||||
<CardActionArea component={Link} href={`/paroles/${slug}`}>
|
||||
<Box sx={{position: 'relative', width: '100%', aspectRatio: {xs: '1 / 1', sm: '16 / 9'}, maxHeight: {sm: 420}}}>
|
||||
{fmt?.url ? (
|
||||
<Image
|
||||
src={`${IMAGE_URL}${fmt.url}`}
|
||||
alt={titre}
|
||||
fill
|
||||
priority
|
||||
placeholder='blur'
|
||||
blurDataURL={BLUR_DATA_URL}
|
||||
sizes='(max-width: 600px) 100vw, 800px'
|
||||
style={{objectFit: 'cover'}}
|
||||
/>
|
||||
) : (
|
||||
<Box sx={{width: '100%', height: '100%', bgcolor: 'grey.300'}} />
|
||||
)}
|
||||
</Box>
|
||||
<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
|
||||
}
|
||||
+100
-21
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import {useState} from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import PropTypes from 'prop-types'
|
||||
import Accordion from '@mui/material/Accordion'
|
||||
@@ -15,52 +16,91 @@ import Grid from '@mui/material/Grid'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Avatar from '@mui/material/Avatar'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import {green} from '@mui/material/colors'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace'
|
||||
import VerifiedIcon from '@mui/icons-material/Verified'
|
||||
|
||||
import {formatKuveti} from '../../lib/kuveti'
|
||||
import {StreamButton} from '../streaming-buttons'
|
||||
import AwtisBiyografi from './awtis-biyografi'
|
||||
import MizikLyen from './mizik-lyen'
|
||||
|
||||
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
||||
const noImageUrl = 'https://place-hold.it/140x140?text=Indisponible'
|
||||
const EXCLUSIVE_LABEL = process.env.NEXT_PUBLIC_EXCLUSIVE_ARTIST_LABEL || 'OKI Exclusif'
|
||||
const BLUR_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNsYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
|
||||
|
||||
const sortTeks = paroles => paroles.sort((a, b) => a.titre.localeCompare(b.titre, 'fr', {sensitivity: 'base'}))
|
||||
|
||||
export default function AwtisDetay({anAwtis}) {
|
||||
const [esByografiOuve, meteEsByografiOuve] = useState(false)
|
||||
const {alias, biographie, paroles, photo} = anAwtis
|
||||
const {alias, biographie, paroles, photo, isExclusiveArtist, titrePhare} = anAwtis
|
||||
const sortedTeks = sortTeks(paroles)
|
||||
const gwanBiyo = biographie && biographie.length > 100
|
||||
|
||||
const biyo = gwanBiyo ? `${biographie.slice(0, 100)}...` : biographie
|
||||
|
||||
const handleClick = () => {
|
||||
meteEsByografiOuve(true)
|
||||
}
|
||||
const hasStreaming = isExclusiveArtist && titrePhare?.streamAudio?.length > 0
|
||||
const coverUrl = titrePhare?.couverture
|
||||
? `${IMAGE_URL}${titrePhare.couverture.formats?.small?.url || titrePhare.couverture.formats?.thumbnail?.url || titrePhare.couverture.url}`
|
||||
: null
|
||||
|
||||
const photoUrl = photo?.url
|
||||
? `${IMAGE_URL}${photo.formats?.small?.url || photo.formats?.thumbnail?.url || photo.url}`
|
||||
: null
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Box sx={{marginTop: 8, marginBottom: 2}}>
|
||||
<Box sx={{mt: 8, mb: 2}}>
|
||||
<Typography sx={{textAlign: 'center'}} variant='h6' component='h1'>
|
||||
{alias}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{justifyContent: 'center', display: 'flex', marginBottom: 2}}>
|
||||
<Avatar
|
||||
src={`${photo?.url ? `${IMAGE_URL}${photo?.url}` : noImageUrl}`}
|
||||
alt={`Photo ${alias}`}
|
||||
sx={{width: 200, height: 200, border: `2px solid ${green[500]}`}}
|
||||
/>
|
||||
|
||||
<Box sx={{display: 'flex', justifyContent: 'center', mb: 2}}>
|
||||
<Box sx={{
|
||||
width: 200, height: 200,
|
||||
borderRadius: '50%',
|
||||
border: `2px solid ${green[500]}`,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
{photoUrl ? (
|
||||
<Image
|
||||
src={photoUrl}
|
||||
alt={`Photo ${alias}`}
|
||||
width={200}
|
||||
height={200}
|
||||
placeholder='blur'
|
||||
blurDataURL={BLUR_DATA_URL}
|
||||
style={{objectFit: 'cover', width: '100%', height: '100%'}}
|
||||
/>
|
||||
) : (
|
||||
<Box sx={{width: '100%', height: '100%', bgcolor: 'grey.300'}} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Grid sx={{alignItems:'center'}} container direction='column' spacing={3}>
|
||||
|
||||
{isExclusiveArtist && (
|
||||
<Box sx={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.75, mb: 2}}>
|
||||
<Chip
|
||||
icon={<VerifiedIcon sx={{fontSize: 16}} />}
|
||||
label={`Artiste ${EXCLUSIVE_LABEL}`}
|
||||
size='small'
|
||||
sx={{bgcolor: '#FFD700', color: '#000', fontWeight: 700, '& .MuiChip-icon': {color: '#000'}}}
|
||||
/>
|
||||
<Typography variant='caption' sx={{color: 'text.secondary', textAlign: 'center'}}>
|
||||
Certains morceaux sont publiés en exclusivité sur PAWÒL-NU, avant toute sortie sur les plateformes de streaming.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Grid sx={{alignItems: 'center'}} container direction='column' spacing={3}>
|
||||
{biyo && (
|
||||
<Grid size={{xs: 12, md: 6}}>
|
||||
<Card sx={{minWidth: 300}}>
|
||||
<CardActionArea onClick={handleClick}>
|
||||
<CardActionArea onClick={() => meteEsByografiOuve(true)}>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant='body1' component='h2'>
|
||||
<strong>Biographie</strong>
|
||||
@@ -73,6 +113,44 @@ export default function AwtisDetay({anAwtis}) {
|
||||
</Card>
|
||||
</Grid>
|
||||
)}
|
||||
{hasStreaming && (
|
||||
<Grid size={{xs: 12, md: 6}}>
|
||||
<Card sx={{overflow: 'hidden'}}>
|
||||
<Grid container>
|
||||
{coverUrl && (
|
||||
<Grid size={{xs: 12, sm: 4}}>
|
||||
<Box sx={{position: 'relative', minHeight: 140, height: '100%'}}>
|
||||
<Image
|
||||
src={coverUrl}
|
||||
alt={titrePhare.titre}
|
||||
fill
|
||||
placeholder='blur'
|
||||
blurDataURL={BLUR_DATA_URL}
|
||||
sizes='200px'
|
||||
style={{objectFit: 'cover'}}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid size={{xs: 12, sm: coverUrl ? 8 : 12}}>
|
||||
<CardContent>
|
||||
<Typography variant='subtitle1' sx={{fontWeight: 700}} gutterBottom>
|
||||
{titrePhare.titre}
|
||||
</Typography>
|
||||
<Typography variant='caption' sx={{color: 'text.secondary', display: 'block', mb: 1.5}}>
|
||||
Écouter sur
|
||||
</Typography>
|
||||
<Box sx={{display: 'flex', flexWrap: 'wrap', gap: 1}}>
|
||||
{titrePhare.streamAudio.map((lyen, i) => (
|
||||
<StreamButton key={i} lyen={lyen} />
|
||||
))}
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid size={{xs: 12, md: 6}}>
|
||||
<Box marginbottom={3}>
|
||||
{paroles.length > 1 ? (
|
||||
@@ -82,14 +160,13 @@ export default function AwtisDetay({anAwtis}) {
|
||||
aria-controls='panel-teks-content'
|
||||
id='panel-teks-header'
|
||||
>
|
||||
<Typography sx={{marginRight: 2, textAlign:'center' }} variant='body1' component='h2'><strong>Liste des paroles</strong></Typography>
|
||||
<Typography sx={{marginRight: 2, textAlign: 'center'}} variant='body1' component='h2'><strong>Liste des paroles</strong></Typography>
|
||||
<Chip color='primary' label={paroles.length} size='small' variant='contained' />
|
||||
</AccordionSummary>
|
||||
<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}}>
|
||||
<MizikLyen anPawol={anPawol} kuveti={kuvetiFormat} />
|
||||
@@ -105,7 +182,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>
|
||||
)
|
||||
@@ -113,13 +190,15 @@ export default function AwtisDetay({anAwtis}) {
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box sx={{textAlign: 'center', marginBlock: 3}} >
|
||||
|
||||
<Box sx={{textAlign: 'center', marginBlock: 3}}>
|
||||
<Link passHref href='/awtis'>
|
||||
<Button variant='outlined' startIcon={<KeyboardBackspaceIcon />}>
|
||||
Retour aux artistes
|
||||
</Button>
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
{esByografiOuve && (
|
||||
<AwtisBiyografi
|
||||
alias={alias}
|
||||
|
||||
@@ -10,14 +10,17 @@ import Card from '@mui/material/Card'
|
||||
import CardMedia from '@mui/material/CardMedia'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Chip from '@mui/material/Chip'
|
||||
|
||||
import {styled} from '@mui/material/styles'
|
||||
import VerifiedIcon from '@mui/icons-material/Verified'
|
||||
|
||||
import AwtisBiyografi from './awtis-biyografi'
|
||||
|
||||
const PREFIX = 'awtis-kat'
|
||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3001'
|
||||
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
||||
const EXCLUSIVE_LABEL = process.env.NEXT_PUBLIC_EXCLUSIVE_ARTIST_LABEL || 'OKI Exclusif'
|
||||
|
||||
const classes = {
|
||||
root: `${PREFIX}-root`,
|
||||
@@ -55,18 +58,32 @@ export default function AwtisKat({artiste}) {
|
||||
const router = useRouter()
|
||||
const [esByografiOuve, meteEsByografiOuve] = useState(false)
|
||||
|
||||
const {alias, biographie, paroles, photo, slug} = artiste
|
||||
const {alias, biographie, paroles, photo, slug, isExclusiveArtist} = artiste
|
||||
|
||||
return (
|
||||
<Grid size={{xs: 12, sm: 6, md: 4}}>
|
||||
<Kat>
|
||||
<Card sx={{maxWidth: 340}}>
|
||||
<CardActionArea onClick={() => router.push(`${SITE_URL}/awtis/${slug}`)}>
|
||||
<Card sx={{maxWidth: 340, position: 'relative', ...(isExclusiveArtist && {outline: '2px solid #FFD700'})}}>
|
||||
{isExclusiveArtist && (
|
||||
<Chip
|
||||
icon={<VerifiedIcon sx={{fontSize: 14}} />}
|
||||
label={EXCLUSIVE_LABEL}
|
||||
size='small'
|
||||
sx={{
|
||||
position: 'absolute', top: 8, left: 8, zIndex: 1,
|
||||
bgcolor: '#FFD700', color: '#000',
|
||||
fontWeight: 700, fontSize: '0.7rem',
|
||||
'& .MuiChip-icon': {color: '#000'},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<CardActionArea onClick={() => router.push(`/awtis/${slug}`)}>
|
||||
<CardMedia
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -8,11 +8,13 @@ import Box from '@mui/material/Box'
|
||||
import DialogActions from '@mui/material/DialogActions'
|
||||
import DialogContent from '@mui/material/DialogContent'
|
||||
import DialogTitle from '@mui/material/DialogTitle'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import Link from '@mui/material/Link'
|
||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||
import {useTheme} from '@mui/material/styles'
|
||||
import LicensesInfo from './licenses-infos'
|
||||
|
||||
export default function LicenseModal({license}) {
|
||||
export default function LicenseModal({license, sourceOriginale, remixes}) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const theme = useTheme()
|
||||
const fullScreen = useMediaQuery(theme.breakpoints.down('md'))
|
||||
@@ -53,6 +55,48 @@ export default function LicenseModal({license}) {
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{sourceOriginale && (
|
||||
<Box sx={{mb: 3}}>
|
||||
<Typography variant='overline' color='text.secondary' display='block' gutterBottom>
|
||||
Basé sur
|
||||
</Typography>
|
||||
<Link href={`/paroles/${sourceOriginale.slug}`} underline='hover' color='inherit'>
|
||||
<Typography variant='h6' fontWeight='bold'>{sourceOriginale.titre}</Typography>
|
||||
</Link>
|
||||
{sourceOriginale.artistes?.length > 0 && (
|
||||
<Typography variant='body1' color='text.secondary'>
|
||||
{sourceOriginale.artistes.map(a => a.alias).join(', ')}
|
||||
</Typography>
|
||||
)}
|
||||
{sourceOriginale.annee && (
|
||||
<Typography variant='body2' color='text.secondary'>{sourceOriginale.annee}</Typography>
|
||||
)}
|
||||
<Divider sx={{mt: 2}} />
|
||||
</Box>
|
||||
)}
|
||||
{remixes && (
|
||||
<Box sx={{mb: 3}}>
|
||||
<Typography variant='overline' color='text.secondary' display='block' gutterBottom>
|
||||
Déclinaisons
|
||||
</Typography>
|
||||
{remixes.map(remix => (
|
||||
<Box key={remix.slug} sx={{mb: 1.5}}>
|
||||
<Link href={`/paroles/${remix.slug}`} underline='hover' color='inherit'>
|
||||
<Typography variant='h6' fontWeight='bold'>{remix.titre}</Typography>
|
||||
</Link>
|
||||
{remix.artistes?.length > 0 && (
|
||||
<Typography variant='body1' color='text.secondary'>
|
||||
{remix.artistes.map(a => a.alias).join(', ')}
|
||||
</Typography>
|
||||
)}
|
||||
{remix.annee && (
|
||||
<Typography variant='body2' color='text.secondary'>{remix.annee}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
<Divider sx={{mt: 2}} />
|
||||
</Box>
|
||||
)}
|
||||
<LicensesInfo license={license} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@@ -66,5 +110,7 @@ export default function LicenseModal({license}) {
|
||||
}
|
||||
|
||||
LicenseModal.propTypes = {
|
||||
license: PropTypes.string.isRequired
|
||||
license: PropTypes.string.isRequired,
|
||||
sourceOriginale: PropTypes.object,
|
||||
remixes: PropTypes.array
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import {useRef, useEffect} from 'react'
|
||||
import {styled, useTheme} from '@mui/material/styles'
|
||||
import PropTypes from 'prop-types'
|
||||
import {Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography} from '@mui/material'
|
||||
import {useRouter} from 'next/navigation'
|
||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||
import Cgu from '.'
|
||||
|
||||
@@ -23,17 +22,11 @@ const CGU_DOWNLOAD_LINK = process.env.NEXT_PUBLIC_CGU_DOWNLOAD_LINK
|
||||
export default function CGUDialog({open, setOpen}) {
|
||||
const theme = useTheme()
|
||||
const fullScreen = useMediaQuery(theme.breakpoints.down('md'))
|
||||
const router = useRouter()
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleClick = event => {
|
||||
event.preventDefault()
|
||||
router.push(CGU_DOWNLOAD_LINK)
|
||||
}
|
||||
|
||||
const descriptionElementRef = useRef(null)
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@@ -64,7 +57,24 @@ export default function CGUDialog({open, setOpen}) {
|
||||
id='scroll-dialog-description'
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Cgu />
|
||||
{CGU_DOWNLOAD_LINK ? (
|
||||
<div style={{textAlign: 'center', padding: '2rem'}}>
|
||||
<Typography paragraph>
|
||||
Consultez nos CGU et notre politique de confidentialité :
|
||||
</Typography>
|
||||
<Button
|
||||
variant='contained'
|
||||
component='a'
|
||||
href={CGU_DOWNLOAD_LINK}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
Consulter les CGU
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Cgu />
|
||||
)}
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function Cgu() {
|
||||
Le responsable publication est une personne morale
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
<strong>Réalisation</strong> : ORGANISATION KA INTERNATIONALE (OKI)
|
||||
<strong>Réalisation</strong> : Cédric Pronzola
|
||||
</Typography>
|
||||
<List>
|
||||
<ListItem>
|
||||
@@ -79,7 +79,7 @@ export default function Cgu() {
|
||||
<ArrowRightAltIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
<strong>Courriel</strong> : <Link href='mailto:kontak@o-k-i.net'><strong>kontak@o-k-i.net</strong></Link>
|
||||
<strong>Courriel</strong> : <Link href='mailto:contact@cedric-pronzola.dev'><strong>contact@cedric-pronzola.dev</strong></Link>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
@@ -87,7 +87,7 @@ export default function Cgu() {
|
||||
<ArrowRightAltIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
<strong>WEB</strong> : <Link target='_blank' rel='noreferrer' href='https://o-k-i.net'><strong>o-k-i.net</strong></Link>
|
||||
<strong>WEB</strong> : <Link target='_blank' rel='noreferrer' href='https://cedric-pronzola.dev'><strong>cedric-pronzola.dev</strong></Link>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
@@ -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>
|
||||
@@ -585,7 +585,7 @@ export default function Cgu() {
|
||||
</Typography>
|
||||
|
||||
<Typography gutterBottom variant='caption' >
|
||||
Dernières modifications le 12/05/2026
|
||||
Dernières modifications le 26/06/2026
|
||||
</Typography>
|
||||
</Root>
|
||||
)
|
||||
|
||||
+162
-84
@@ -1,9 +1,12 @@
|
||||
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 {useTheme, useColorScheme, styled} from '@mui/material/styles'
|
||||
import Skeleton from '@mui/material/Skeleton'
|
||||
import LinearProgress from '@mui/material/LinearProgress'
|
||||
import {useColorScheme, styled} from '@mui/material/styles'
|
||||
import Table from '@mui/material/Table'
|
||||
import TableHead from '@mui/material/TableHead'
|
||||
import TableBody from '@mui/material/TableBody'
|
||||
@@ -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}`]: {
|
||||
@@ -47,8 +50,10 @@ 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 +68,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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
)
|
||||
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 variant='caption' sx={{
|
||||
backgroundColor: isHaute ? '#07332f' : '#393940',
|
||||
color: isHaute ? '#21feec' : '#fff',
|
||||
borderRadius: '0.66rem',
|
||||
border: mode === 'dark' ? `1px solid ${isHaute ? '#21feec' : '#fff'}` : 'none',
|
||||
padding: '0.15rem 0.5rem',
|
||||
fontWeight: 'bold',
|
||||
letterSpacing: '0.05rem',
|
||||
textTransform: 'uppercase',
|
||||
}}>{isHaute ? 'Haute' : 'Faible'}</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
case '.flac':
|
||||
if (caption === 'MAX') {
|
||||
return (
|
||||
<Typography sx={{
|
||||
backgroundColor: '#332619',
|
||||
color: '#ffbe7d',
|
||||
borderRadius: '0.66rem',
|
||||
border: mode === 'dark' ? '1px solid #ffbe7d' : 'none',
|
||||
fontSize: '0.75rem',
|
||||
letterSpacing: '0.1rem',
|
||||
padding: '0.2515rem 0.6707rem',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>{caption}</Typography>
|
||||
)
|
||||
}
|
||||
const renderMeta = file => {
|
||||
const format = file.ext.replace('.', '').toUpperCase()
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
|
||||
default:
|
||||
return <DescriptionIcon sx={{marginRight: 1}} />
|
||||
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 (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
@@ -151,7 +210,7 @@ export default function FilesList({files}) {
|
||||
component='nav'
|
||||
aria-labelledby='nested-list-subheader'
|
||||
>
|
||||
<ListSubheader disableSticky sx={{backgroundColor: mode === 'light' ? theme.palette.grey[100] : theme.palette.background.default}} color='primary'>
|
||||
<ListSubheader disableSticky sx={{bgcolor: 'background.paper'}}>
|
||||
<Box paddingBlock={1} display='flex' justifyContent='center'>
|
||||
<LibraryMusicIcon />
|
||||
<Typography gutterBottom marginLeft={1} variant='button'>Musiques</Typography>
|
||||
@@ -159,10 +218,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 +228,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>
|
||||
))}
|
||||
@@ -198,7 +276,7 @@ export default function FilesList({files}) {
|
||||
component='nav'
|
||||
aria-labelledby='nested-list-subheader'
|
||||
>
|
||||
<ListSubheader disableSticky sx={{marginTop: 2, backgroundColor: mode === 'light' ? theme.palette.grey[100] : theme.palette.background.default}} color='primary'>
|
||||
<ListSubheader disableSticky sx={{marginTop: 2, bgcolor: 'background.paper'}}>
|
||||
<Box paddingBlock={1} display='flex' justifyContent='center' alignSelf='center'>
|
||||
<DescriptionIcon />
|
||||
<Typography gutterBottom marginLeft={1} variant='button'>Paroles</Typography>
|
||||
|
||||
+15
-12
@@ -5,6 +5,9 @@ import Navigasyon from './navigasyon'
|
||||
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
||||
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || 'PAWÒL-NU. Paroles et traductions.'
|
||||
const orgName = process.env.NEXT_PUBLIC_ORG_NAME || 'OKI'
|
||||
const twitterHandle = `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`
|
||||
|
||||
export default function HeadLayout({
|
||||
children,
|
||||
@@ -20,38 +23,38 @@ export default function HeadLayout({
|
||||
return (
|
||||
<div>
|
||||
<Head prefix='website: https://ogp.me/ns/website#'>
|
||||
<title>{`${title ? `PAWÒL-NU | ${title}` : 'PAWÒL-NU. Paroles et traductions.'}`}</title>
|
||||
<title>{`${title ? `${siteName} | ${title}` : siteName}`}</title>
|
||||
<link rel='canonical' href={`${slug ? `${siteUrl}/${slug}` : siteUrl}`} />
|
||||
<link rel='manifest' href='/manifest.json' />
|
||||
<link rel='icon' type='image/x-icon' sizes='32x32' href='/favicon.ico' />
|
||||
<link rel='apple-touch-icon' href='/favicon.ico' />
|
||||
<meta name='monetization' content='$ilp.uphold.com/q7MFmYWNpwnr' />
|
||||
<meta name='application-name' content='PAWÒL-NU. Paroles et traductions.' />
|
||||
<meta name='application-name' content={siteName} />
|
||||
<meta name='twitter:card' content='summary_large_image' />
|
||||
<meta name='twitter:url' content={`${slug ? `${siteUrl}/${slug}` : siteUrl}`} />
|
||||
<meta name='twitter:title' content={`${title ? title : 'PAWÒL-NU. Paroles et traductions.'}`} />
|
||||
<meta name='twitter:description' content={`${summary ? summary : 'PAWÒL-NU a pour but de promouvoir les langues et les productions afro-diasporiques.'}`} />
|
||||
<meta name='twitter:title' content={`${title ? title : siteName}`} />
|
||||
<meta name='twitter:description' content={`${summary ? summary : process.env.NEXT_PUBLIC_SITE_DESCRIPTION || 'PAWÒL-NU a pour but de promouvoir les langues et les productions afro-diasporiques.'}`} />
|
||||
<meta name='twitter:image' content={`${imageUrl ? `${apiUrl}${imageUrl}` : `${siteUrl}/logo-192x192.png`}`} />
|
||||
<meta name='twitter:creator' content='@OrganisationKA' />
|
||||
<meta name='twitter:site' content='@OrganisationKA' />
|
||||
<meta name='twitter:creator' content={twitterHandle} />
|
||||
<meta name='twitter:site' content={twitterHandle} />
|
||||
<meta name='theme-color' content='#303030' />
|
||||
<meta name='apple-mobile-web-app-status-bar' content='#303030' />
|
||||
<meta charSet='utf-8' />
|
||||
<meta name='description' content={`${summary ? summary : 'PAWÒL-NU a pour but de promouvoir les langues et les productions afro-diasporiques.'}`} />
|
||||
<meta name='author' content='OKI' />
|
||||
<meta name='description' content={`${summary ? summary : process.env.NEXT_PUBLIC_SITE_DESCRIPTION || 'PAWÒL-NU a pour but de promouvoir les langues et les productions afro-diasporiques.'}`} />
|
||||
<meta name='author' content={orgName} />
|
||||
<meta name='viewport' content='minimum-scale=1, initial-scale=1, width=device-width' />
|
||||
<meta property='og:url' content={`${slug ? `${siteUrl}/${slug}` : siteUrl}`} />
|
||||
<meta property='og:type' content='website' />
|
||||
<meta property='og:site_name' content={`${title ? title : 'PAWÒL-NU. Paroles et traductions.'}`} />
|
||||
<meta property='og:title' content={`${title ? title : 'PAWÒL-NU. Paroles et traductions.'}`} />
|
||||
<meta property='og:description' content={`${summary ? summary : 'PAWÒL-NU a pour but de promouvoir les langues et les productions afro-diasporiques.'}`} />
|
||||
<meta property='og:site_name' content={`${title ? title : siteName}`} />
|
||||
<meta property='og:title' content={`${title ? title : siteName}`} />
|
||||
<meta property='og:description' content={`${summary ? summary : process.env.NEXT_PUBLIC_SITE_DESCRIPTION || 'PAWÒL-NU a pour but de promouvoir les langues et les productions afro-diasporiques.'}`} />
|
||||
<meta property='og:locale' content='fr_FR' />
|
||||
<meta property='og:image' content={`${imageUrl ? `${apiUrl}${imageUrl}` : `${siteUrl}/logo-512x512.png`}`} />
|
||||
<meta property='og:image:secure_url' content={`${imageUrl ? `${apiUrl}${imageUrl}` : `${siteUrl}/logo-512x512.png`}`} />
|
||||
<meta property='og:image:type' content={imageMime ? imageMime : 'image/png'} />
|
||||
<meta property='og:image:width' content={imageWidth ? imageWidth : '512'} />
|
||||
<meta property='og:image:height' content={imageHeight ? imageHeight : '512'} />
|
||||
<meta property='og:image:alt' content={`${title && imageUrl ? title : 'PAWÒL-NU Logo'} | PAWÒL-NU. Paroles et traductions.`} />
|
||||
<meta property='og:image:alt' content={`${title && imageUrl ? title : `${siteName} Logo`} | ${siteName}`} />
|
||||
</Head>
|
||||
<Navigasyon selectedTab={tab} />
|
||||
{children}
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function RezoDialog() {
|
||||
onClose={handleClose}
|
||||
>
|
||||
<BootstrapDialogTitle id='rézo-dialog' onClose={handleClose}>
|
||||
OKI sur le <strong>Fédiverse</strong>
|
||||
{process.env.NEXT_PUBLIC_ORG_NAME || 'OKI'} sur le <strong>Fédiverse</strong>
|
||||
</BootstrapDialogTitle>
|
||||
<DialogContent>
|
||||
<Box>
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function Presantasyon() {
|
||||
</ListItem>
|
||||
</List>
|
||||
<Typography paragraph='true' variant='subtitle1' component='div'>
|
||||
Pour toute question, n’hésitez pas à nous contacter par courriel <Link href='mailto:kontak@o-k-i.net'><strong>kontak@o-k-i.net</strong></Link>.
|
||||
Pour toute question, n’hésitez pas à nous contacter par courriel <Link href={`mailto:${process.env.NEXT_PUBLIC_ORG_EMAIL || 'kontak@o-k-i.net'}`}><strong>{process.env.NEXT_PUBLIC_ORG_EMAIL || 'kontak@o-k-i.net'}</strong></Link>.
|
||||
</Typography>
|
||||
<Typography paragraph='true' variant='subtitle1' component='div'>
|
||||
<strong>Merci par avance pour votre soutien 🥰</strong>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
import Button from '@mui/material/Button'
|
||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
||||
import {Spotify, Applemusic, Deezer, Tidal, Youtubemusic, Amazon, Soundcloud} from '@icons-pack/react-simple-icons'
|
||||
|
||||
export const PLATFORM_CONFIG = {
|
||||
Spotify: {label: 'Spotify', bg: '#1DB954', color: '#fff', Icon: Spotify},
|
||||
Applemusic: {label: 'Apple Music', bg: '#FC3C44', color: '#fff', Icon: Applemusic},
|
||||
Deezer: {label: 'Deezer', bg: '#EF5466', color: '#fff', Icon: Deezer},
|
||||
Tidal: {label: 'Tidal', bg: '#000000', color: '#fff', Icon: Tidal},
|
||||
Qobuz: {label: 'Qobuz', bg: '#00245B', color: '#fff', Icon: null},
|
||||
Youtubemusic: {label: 'YouTube Music', bg: '#FF0000', color: '#fff', Icon: Youtubemusic},
|
||||
Amazon: {label: 'Amazon Music', bg: '#00A8E1', color: '#fff', Icon: Amazon},
|
||||
Soundcloud: {label: 'SoundCloud', bg: '#FF5500', color: '#fff', Icon: Soundcloud},
|
||||
}
|
||||
|
||||
export function StreamButton({lyen}) {
|
||||
const config = PLATFORM_CONFIG[lyen.plateforme] ?? {label: lyen.plateforme, bg: '#555', color: '#fff', Icon: null}
|
||||
const PlatformIcon = config.Icon
|
||||
return (
|
||||
<Button
|
||||
href={lyen.url}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
size='small'
|
||||
startIcon={PlatformIcon ? <PlatformIcon size={16} color={config.color} /> : null}
|
||||
endIcon={<OpenInNewIcon sx={{fontSize: 11}} />}
|
||||
sx={{
|
||||
bgcolor: config.bg,
|
||||
color: config.color,
|
||||
fontWeight: 700,
|
||||
fontSize: '0.72rem',
|
||||
textTransform: 'none',
|
||||
'&:hover': {bgcolor: config.bg, opacity: 0.85},
|
||||
}}
|
||||
>
|
||||
{config.label}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
'use client'
|
||||
|
||||
import {useState, useEffect, forwardRef} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Fab from '@mui/material/Fab'
|
||||
import Dialog from '@mui/material/Dialog'
|
||||
import Slide from '@mui/material/Slide'
|
||||
import Box from '@mui/material/Box'
|
||||
import Skeleton from '@mui/material/Skeleton'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||
import {useTheme, keyframes} from '@mui/material/styles'
|
||||
import MicIcon from '@mui/icons-material/Mic'
|
||||
import CloseIcon from '@mui/icons-material/Close'
|
||||
|
||||
const pulse = keyframes`
|
||||
0%, 100% { opacity: 0.18; transform: scale(1); }
|
||||
50% { opacity: 0.55; transform: scale(1.2); }
|
||||
`
|
||||
|
||||
const SlideUp = forwardRef(function SlideUp(props, ref) {
|
||||
return <Slide direction='up' ref={ref} {...props} />
|
||||
})
|
||||
|
||||
function toEmbedUrl(url) {
|
||||
try {
|
||||
const u = new URL(url)
|
||||
const match = u.pathname.match(/\/(?:videos\/watch|w)\/([^/?#]+)/)
|
||||
if (match) {
|
||||
return `${u.origin}/videos/embed/${match[1]}?title=0&warningTitle=0&peertubeLink=0&controlBar=1`
|
||||
}
|
||||
return url
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export default function KaraokeModal({url, desktopUrl, titre, artistes}) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const theme = useTheme()
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
|
||||
const activeUrl = (!isMobile && desktopUrl) ? desktopUrl : url
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) setLoaded(false)
|
||||
}, [open])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title='Karaoké' placement='left'>
|
||||
<Fab
|
||||
color='primary'
|
||||
size={isMobile ? 'medium' : 'large'}
|
||||
aria-label='karaoké'
|
||||
onClick={() => setOpen(true)}
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: {xs: 24, sm: 32},
|
||||
right: {xs: 24, sm: 32},
|
||||
zIndex: 1050,
|
||||
}}
|
||||
>
|
||||
<MicIcon />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
slots={{transition: SlideUp}}
|
||||
maxWidth={false}
|
||||
sx={{
|
||||
'& .MuiDialog-container': {
|
||||
alignItems: {xs: 'flex-end', sm: 'center'},
|
||||
}
|
||||
}}
|
||||
slotProps={{
|
||||
backdrop: {sx: {backdropFilter: 'blur(6px)', bgcolor: 'rgba(0,0,0,0.85)'}},
|
||||
paper: {
|
||||
sx: {
|
||||
bgcolor: '#000',
|
||||
overflow: 'hidden',
|
||||
...(isMobile ? {
|
||||
width: '100vw',
|
||||
height: '100dvh',
|
||||
maxWidth: 'none',
|
||||
maxHeight: 'none',
|
||||
m: 0,
|
||||
borderRadius: 0,
|
||||
boxShadow: 'none',
|
||||
} : {
|
||||
width: 'calc(80vh * 16 / 9)',
|
||||
height: '80vh',
|
||||
maxWidth: 'none',
|
||||
maxHeight: 'none',
|
||||
borderRadius: '16px',
|
||||
boxShadow: '0 24px 64px rgba(0,0,0,0.7)',
|
||||
})
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Header superposé en gradient */}
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 2,
|
||||
px: 2,
|
||||
pt: 2,
|
||||
pb: 4,
|
||||
background: 'linear-gradient(to bottom, rgba(0,0,0,0.8) 0%, transparent 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-between',
|
||||
pointerEvents: 'none',
|
||||
}}>
|
||||
<Box sx={{pointerEvents: 'none'}}>
|
||||
{titre && (
|
||||
<Typography variant='subtitle1' sx={{color: '#fff', fontWeight: 700, lineHeight: 1.2, textShadow: '0 1px 4px rgba(0,0,0,0.5)'}}>
|
||||
{titre}
|
||||
</Typography>
|
||||
)}
|
||||
{artistes?.length > 0 && (
|
||||
<Typography variant='caption' sx={{color: 'rgba(255,255,255,0.65)', textShadow: '0 1px 4px rgba(0,0,0,0.5)'}}>
|
||||
{artistes.map(a => a.alias).join(', ')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<IconButton
|
||||
size='small'
|
||||
onClick={() => setOpen(false)}
|
||||
aria-label='fermer'
|
||||
sx={{
|
||||
pointerEvents: 'auto',
|
||||
color: '#fff',
|
||||
bgcolor: 'rgba(255,255,255,0.15)',
|
||||
backdropFilter: 'blur(8px)',
|
||||
border: '1px solid rgba(255,255,255,0.12)',
|
||||
'&:hover': {bgcolor: 'rgba(255,255,255,0.25)'},
|
||||
}}
|
||||
>
|
||||
<CloseIcon fontSize='small' />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
{open && (
|
||||
<Box sx={{position: 'absolute', inset: 0, overflow: 'hidden'}}>
|
||||
{!loaded && (
|
||||
<>
|
||||
<Skeleton
|
||||
variant='rectangular'
|
||||
animation='wave'
|
||||
sx={{position: 'absolute', inset: 0, bgcolor: 'rgba(255,255,255,0.06)', transform: 'none'}}
|
||||
/>
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 1.5,
|
||||
}}>
|
||||
<MicIcon sx={{fontSize: 44, color: 'rgba(255,255,255,0.55)', animation: `${pulse} 1.6s ease-in-out infinite`}} />
|
||||
<Typography
|
||||
variant='caption'
|
||||
sx={{color: 'rgba(255,255,255,0.35)', letterSpacing: '0.15em', textTransform: 'uppercase'}}
|
||||
>
|
||||
Chargement…
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isMobile ? (
|
||||
/* Vidéo verticale : scale le player 16:9 à la hauteur du portrait et croppe les côtés */
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
height: '100%',
|
||||
aspectRatio: '16 / 9',
|
||||
}}>
|
||||
<iframe
|
||||
src={toEmbedUrl(activeUrl)}
|
||||
onLoad={() => setLoaded(true)}
|
||||
style={{width: '100%', height: '100%', border: 'none', display: 'block', opacity: loaded ? 1 : 0, transition: 'opacity 0.4s ease'}}
|
||||
allowFullScreen
|
||||
allow='autoplay; fullscreen'
|
||||
sandbox='allow-same-origin allow-scripts allow-popups allow-forms'
|
||||
title='Karaoké'
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
/* Vidéo 16:9 desktop : player remplit exactement le dialog paysage */
|
||||
<iframe
|
||||
src={toEmbedUrl(activeUrl)}
|
||||
onLoad={() => setLoaded(true)}
|
||||
style={{width: '100%', height: '100%', border: 'none', display: 'block', opacity: loaded ? 1 : 0, transition: 'opacity 0.4s ease'}}
|
||||
allowFullScreen
|
||||
allow='autoplay; fullscreen'
|
||||
sandbox='allow-same-origin allow-scripts allow-popups allow-forms'
|
||||
title='Karaoké'
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
KaraokeModal.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
desktopUrl: PropTypes.string,
|
||||
titre: PropTypes.string,
|
||||
artistes: PropTypes.array,
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ import VweKouteAchte from './vwe-koute-achte'
|
||||
|
||||
import DrawerBar from './drawer-bar'
|
||||
import Pataje from './pataje'
|
||||
import KaraokeModal from './karaoke-modal'
|
||||
|
||||
const drawerWidth = 240
|
||||
|
||||
@@ -118,9 +119,6 @@ export default function TeksDrawer({paroles}) {
|
||||
<Drawer
|
||||
variant='temporary'
|
||||
open={mobileOpen}
|
||||
ModalProps={{
|
||||
keepMounted: true
|
||||
}}
|
||||
sx={{
|
||||
display: {xs: 'block', sm: 'none'},
|
||||
'& .MuiDrawer-paper': {boxSizing: 'border-box', width: drawerWidth}
|
||||
@@ -140,6 +138,9 @@ export default function TeksDrawer({paroles}) {
|
||||
<DrawerBar meteEsMobilOuve={setMobileOpen} paroles={paroles} />
|
||||
</Drawer>
|
||||
</Box>
|
||||
{parole?.karaokeUrl && (
|
||||
<KaraokeModal url={parole.karaokeUrl} desktopUrl={parole.karaokeDesktopUrl} titre={parole.titre} artistes={parole.artistes} />
|
||||
)}
|
||||
{success && (
|
||||
<Snackbar open={open} autoHideDuration={3000} onClose={handleClose}>
|
||||
<Alert severity='success' onClose={handleClose}>
|
||||
|
||||
+18
-16
@@ -9,7 +9,6 @@ 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 Grid from '@mui/material/Grid'
|
||||
@@ -23,26 +22,21 @@ const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337
|
||||
|
||||
const classes = {
|
||||
root: `${PREFIX}-root`,
|
||||
media: `${PREFIX}-media`
|
||||
}
|
||||
|
||||
const StyledGrid = styled(Grid)({
|
||||
[`& .${classes.root}`]: {
|
||||
maxWidth: 345
|
||||
},
|
||||
[`& .${classes.media}`]: {
|
||||
height: 240,
|
||||
objectFit: 'contain'
|
||||
}
|
||||
})
|
||||
|
||||
const noImageUrl = 'https://place-hold.it/140x140?text=Indisponible'
|
||||
const BLUR_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNsYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
|
||||
|
||||
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 => {
|
||||
@@ -53,13 +47,21 @@ export default function TeksKat({parole}) {
|
||||
<StyledGrid size={{xs: 12, sm: 6, md: 4}}>
|
||||
<Card className={classes.root}>
|
||||
<CardActionArea onClick={() => handleClick(slug)}>
|
||||
<CardMedia
|
||||
className={classes.media}
|
||||
component='img'
|
||||
alt={titre}
|
||||
image={couverture?.url ? `${IMAGE_URL}${couverture.url}` : noImageUrl}
|
||||
title={titre}
|
||||
/>
|
||||
<Box sx={{position: 'relative', height: 240}}>
|
||||
{couverture?.url ? (
|
||||
<Image
|
||||
src={`${IMAGE_URL}${couverture.formats?.thumbnail?.url || couverture.url}`}
|
||||
alt={titre}
|
||||
fill
|
||||
placeholder='blur'
|
||||
blurDataURL={BLUR_DATA_URL}
|
||||
sizes='(max-width: 600px) 100vw, (max-width: 900px) 50vw, 33vw'
|
||||
style={{objectFit: 'contain'}}
|
||||
/>
|
||||
) : (
|
||||
<Box sx={{width: '100%', height: '100%', bgcolor: 'grey.300'}} />
|
||||
)}
|
||||
</Box>
|
||||
<CardContent>
|
||||
<Box sx={{display: 'flex', alignItems: 'center'}}>
|
||||
<Typography display='inline' style={{marginRight: 5}} variant='h6' component='h2'>
|
||||
|
||||
+47
-34
@@ -13,14 +13,20 @@ 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'
|
||||
import {StreamButton} from '../streaming-buttons'
|
||||
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 = {
|
||||
@@ -72,30 +78,32 @@ const Root = styled('div')((
|
||||
},
|
||||
}))
|
||||
|
||||
const LANG_NAMES = {fr: 'Français', en: 'English', es: 'Español', de: 'Deutsch', it: 'Italiano'}
|
||||
|
||||
const langToArray = parole => {
|
||||
const langArray = []
|
||||
|
||||
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: 'English', lang: anglais})
|
||||
}
|
||||
|
||||
if (anglais) {
|
||||
langArray.push({title: 'Translation', flag: 'en', lang: anglais})
|
||||
if (francais) {
|
||||
langArray.push({title: 'Français', lang: francais})
|
||||
}
|
||||
|
||||
if (espagnol) {
|
||||
langArray.push({title: 'Traducción', flag: 'es', lang: espagnol})
|
||||
langArray.push({title: 'Español', lang: espagnol})
|
||||
}
|
||||
|
||||
if (allemand) {
|
||||
langArray.push({title: 'Übersetzung', flag: 'de', lang: allemand})
|
||||
langArray.push({title: 'Deutsch', lang: allemand})
|
||||
}
|
||||
|
||||
if (italien) {
|
||||
langArray.push({title: 'Traduzione', flag: 'it', lang: italien})
|
||||
langArray.push({title: 'Italiano', lang: italien})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +134,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 +178,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>
|
||||
@@ -181,7 +201,7 @@ export default function Teks({parole}) {
|
||||
)}
|
||||
{parole.creativeCommons && (
|
||||
<Box sx={{maxWidth: 220, margin: '0 auto', textAlign: 'center'}}>
|
||||
<LicenseModal license={parole.creativeCommons.toLowerCase()} />
|
||||
<LicenseModal license={parole.creativeCommons.toLowerCase()} sourceOriginale={parole.sourceOriginale ?? null} remixes={parole.remixes?.length ? parole.remixes : null} />
|
||||
</Box>
|
||||
)}
|
||||
{parole?.files && (
|
||||
@@ -198,47 +218,40 @@ export default function Teks({parole}) {
|
||||
{parole?.difference?.length > 0 && (
|
||||
<DiferansDialog difference={parole.difference} />
|
||||
)}
|
||||
{parole.streamAudio?.length > 0 && (
|
||||
<Box sx={{mt: 2, mb: 1}}>
|
||||
<Typography variant='overline' sx={{color: 'text.secondary', display: 'block', mb: 1}}>
|
||||
Écouter sur
|
||||
</Typography>
|
||||
<Box sx={{display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 1}}>
|
||||
{parole.streamAudio.map((lyen, i) => (
|
||||
<StreamButton key={i} lyen={lyen} />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Grid container justifycontent='center' spacing={1}>
|
||||
<Grid size={{xs: 12, md: langArray.length > 0 ? 6 : null}}>
|
||||
<Box className={classes.gridText}>
|
||||
<Typography align='center' sx={{marginBottom: '0.5em'}} variant='h4'>
|
||||
Transcription
|
||||
{parole.langueSource && parole.langueSource !== 'ka' && (
|
||||
<Typography component='span' variant='body2' color='text.secondary' sx={{ml: 1}}>
|
||||
({LANG_NAMES[parole.langueSource]})
|
||||
</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography paragraph='true' align={alignTeks(langArray, isMobile)} component='span'>
|
||||
{formatJsonString(parole.transcription)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
{langArray.map(({title, flag, lang}) => (
|
||||
{langArray.map(({title, lang}) => (
|
||||
<Grid key={title} size={{xs: 12, md: 6}}>
|
||||
<Box className={classes.gridText}>
|
||||
<Typography align='center' sx={{marginBottom: '0.5em'}} variant='h4'>
|
||||
{flag === 'fr' && (
|
||||
<span>
|
||||
🇫🇷
|
||||
</span>
|
||||
)}
|
||||
{flag === 'en' && (
|
||||
<span>
|
||||
🇬🇧
|
||||
</span>
|
||||
)}
|
||||
{flag === 'es' && (
|
||||
<span>
|
||||
🇪🇸
|
||||
</span>
|
||||
)}
|
||||
{flag === 'de' && (
|
||||
<span>
|
||||
🇩🇪
|
||||
</span>
|
||||
)}
|
||||
{flag === 'it' && (
|
||||
<span>
|
||||
🇮🇹
|
||||
</span>
|
||||
)} {title}
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography paragraph='true' align='justify' component='span'>
|
||||
{formatJsonString(lang)}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const {template} = require('lodash')
|
||||
|
||||
const ORG_NAME = process.env.NEXT_PUBLIC_ORG_NAME || 'OKI'
|
||||
const SITE_URL = process.env.SITE_URL || process.env.NEXT_PUBLIC_SITE_URL || 'https://pawol.nu'
|
||||
|
||||
const bodyTemplate = template(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
@@ -65,18 +68,18 @@ const bodyTemplate = template(`
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<img src="https://pawol.nu/logo-72x72.png" alt="Logo OKI">
|
||||
<img src="${SITE_URL}/logo-72x72.png" alt="Logo ${ORG_NAME}">
|
||||
</div>
|
||||
<div class="title">
|
||||
<h2 style="margin:0; mso-line-height-rule:exactly;">Merci !</h2><br>
|
||||
<h3 style="margin:0; mso-line-height-rule:exactly;">Votre soutien à OKI est important et nous vous en sommes très reconnaissant ❤️</h3>
|
||||
<h3 style="margin:0; mso-line-height-rule:exactly;">Votre soutien à ${ORG_NAME} est important et nous vous en sommes très reconnaissant ❤️</h3>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<section>
|
||||
<h4>Une aide indispensable</h4>
|
||||
<p>
|
||||
En effet, ce sont les dons qui nous permettent de maintenir tous les services liés à OKI.
|
||||
En effet, ce sont les dons qui nous permettent de maintenir tous les services liés à ${ORG_NAME}.
|
||||
</p>
|
||||
<p>
|
||||
Toute contribution, aussi modeste soit-elle, nous permet d’augmenter nos capacités.<br />
|
||||
@@ -94,7 +97,7 @@ const bodyTemplate = template(`
|
||||
<p>Pour toute question, n’hésitez pas à nous contacter en répondant à ce courriel.</p>
|
||||
<p>Merci infiniment pour votre soutien.</p>
|
||||
|
||||
<span><i><span class="forceWhiteLink"><button><a href="https://pawol.nu" target="blank">PAWÒL-NU</a></button></span></i></span>
|
||||
<span><i><span class="forceWhiteLink"><button><a href="${SITE_URL}" target="blank">${ORG_NAME}</a></button></span></i></span>
|
||||
</footer>
|
||||
</html>
|
||||
`)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
+10
-15
@@ -1,19 +1,14 @@
|
||||
export const formatKuveti = kuveti => {
|
||||
if (!kuveti) {
|
||||
return null
|
||||
}
|
||||
const SIZE_ORDER = {
|
||||
large: ['large', 'medium', 'small'],
|
||||
medium: ['medium', 'small', 'large'],
|
||||
small: ['small', 'medium', 'large'],
|
||||
}
|
||||
|
||||
if (kuveti && kuveti.formats && kuveti.formats.large) {
|
||||
return kuveti.formats.large
|
||||
export const formatKuveti = (kuveti, preferred = 'large') => {
|
||||
if (!kuveti) return null
|
||||
const order = SIZE_ORDER[preferred] ?? SIZE_ORDER.large
|
||||
for (const size of order) {
|
||||
if (kuveti.formats?.[size]) return kuveti.formats[size]
|
||||
}
|
||||
|
||||
if (kuveti && kuveti.formats && kuveti.formats.medium) {
|
||||
return kuveti.formats.medium
|
||||
}
|
||||
|
||||
if (kuveti && kuveti.formats && kuveti.formats.small) {
|
||||
return kuveti.formats.small
|
||||
}
|
||||
|
||||
return kuveti
|
||||
}
|
||||
|
||||
+40
-2
@@ -56,6 +56,25 @@ export async function jwennTeksEpiSlug(slug) {
|
||||
},
|
||||
traductions: {
|
||||
populate: '*'
|
||||
},
|
||||
pawol: {
|
||||
populate: '*'
|
||||
},
|
||||
sourceOriginale: {
|
||||
fields: ['titre', 'slug', 'annee'],
|
||||
populate: {
|
||||
artistes: {
|
||||
fields: ['alias', 'slug']
|
||||
}
|
||||
}
|
||||
},
|
||||
remixes: {
|
||||
fields: ['titre', 'slug', 'annee'],
|
||||
populate: {
|
||||
artistes: {
|
||||
fields: ['alias', 'slug']
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -73,7 +92,7 @@ export async function jwennTeksEpiSlug(slug) {
|
||||
|
||||
export async function jwennAwtisEpiSlug(slug) {
|
||||
const query = qs.stringify({
|
||||
populate: ['paroles', 'photo', 'paroles.couverture'],
|
||||
populate: ['paroles', 'photo', 'paroles.couverture', 'titrePhare', 'titrePhare.streamAudio', 'titrePhare.streamVideo', 'titrePhare.couverture'],
|
||||
filters: {
|
||||
slug: {
|
||||
$eq: slug
|
||||
@@ -92,7 +111,7 @@ export async function jwennAwtisPajinasyon(paj) {
|
||||
const start = AWTIS_POU_CHAK_PAJ * (paj - 1)
|
||||
const query = qs.stringify({
|
||||
populate: ['paroles', 'photo'],
|
||||
sort: ['createdAt:desc'],
|
||||
sort: ['isExclusiveArtist:desc', 'createdAt:desc'],
|
||||
pagination: {
|
||||
start,
|
||||
limit: AWTIS_POU_CHAK_PAJ
|
||||
@@ -210,6 +229,25 @@ export async function jwennDenyeTeks() {
|
||||
return data
|
||||
}
|
||||
|
||||
export async function jwennAnVedette() {
|
||||
const query = qs.stringify({
|
||||
populate: ['artistes', 'couverture'],
|
||||
filters: {
|
||||
isNewRelease: { $eq: true }
|
||||
},
|
||||
pagination: { limit: 1 }
|
||||
}, {
|
||||
encodeValuesOnly: true
|
||||
})
|
||||
|
||||
const {data} = await request(`/paroles?${query}`, {
|
||||
next: {revalidate: 0},
|
||||
headers: {Authorization: `Bearer ${readToken}`}
|
||||
})
|
||||
|
||||
return data?.[0] ?? null
|
||||
}
|
||||
|
||||
export async function jwennAnTeks(teksId, token) {
|
||||
const headers = {
|
||||
'content-type': 'application/json',
|
||||
|
||||
+7
-17
@@ -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 />
|
||||
}
|
||||
]
|
||||
|
||||
+17
-24
@@ -5,6 +5,22 @@ const withPWA = require('next-pwa')({
|
||||
skipWaiting: true
|
||||
})
|
||||
|
||||
function buildRemotePatterns() {
|
||||
const raw = process.env.NEXT_PUBLIC_DOMAINS_IMAGE || ''
|
||||
const patterns = raw.split(' ').filter(Boolean).map(entry => {
|
||||
const [hostname, port] = entry.split(':')
|
||||
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1'
|
||||
const pattern = {protocol: isLocal ? 'http' : 'https', hostname, pathname: '/uploads/**'}
|
||||
if (port) pattern.port = port
|
||||
return pattern
|
||||
})
|
||||
if (!raw.includes('localhost'))
|
||||
patterns.push({protocol: 'http', hostname: 'localhost', port: '1337', pathname: '/uploads/**'})
|
||||
if (!raw.includes('127.0.0.1'))
|
||||
patterns.push({protocol: 'http', hostname: '127.0.0.1', port: '1337', pathname: '/uploads/**'})
|
||||
return patterns
|
||||
}
|
||||
|
||||
module.exports = (withPWA({
|
||||
turbopack: {},
|
||||
webpack: config => {
|
||||
@@ -16,29 +32,6 @@ module.exports = (withPWA({
|
||||
return config
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'api.pawol.nu',
|
||||
pathname: '/uploads/**',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'pawol.nu',
|
||||
},
|
||||
{
|
||||
protocol: 'http',
|
||||
hostname: '127.0.0.1',
|
||||
port: '1337',
|
||||
pathname: '/uploads/**',
|
||||
},
|
||||
{
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: '1337',
|
||||
pathname: '/uploads/**',
|
||||
},
|
||||
]
|
||||
|
||||
remotePatterns: buildRemotePatterns()
|
||||
}
|
||||
}))
|
||||
|
||||
+6
-4
@@ -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",
|
||||
|
||||
Binary file not shown.
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"name": "PAWÒL-NU",
|
||||
"short_name": "PAWÒL-NU",
|
||||
"description": "PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#303030",
|
||||
"theme_color": "#303030",
|
||||
"orientation": "portrait-primary",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logo-72x72.png",
|
||||
"type": "image/png",
|
||||
"sizes": "72x72"
|
||||
},
|
||||
{
|
||||
"src": "/logo-96x96.png",
|
||||
"type": "image/png",
|
||||
"sizes": "96x96"
|
||||
},
|
||||
{
|
||||
"src": "/logo-128x128.png",
|
||||
"type": "image/png",
|
||||
"sizes": "128x128"
|
||||
},
|
||||
{
|
||||
"src": "/logo-144x144.png",
|
||||
"type": "image/png",
|
||||
"sizes": "144x144"
|
||||
},
|
||||
{
|
||||
"src": "/logo-152x152.png",
|
||||
"type": "image/png",
|
||||
"sizes": "152x152"
|
||||
},
|
||||
{
|
||||
"src": "/logo-192x192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/logo-256x256.png",
|
||||
"type": "image/png",
|
||||
"sizes": "256x256"
|
||||
},
|
||||
{
|
||||
"src": "/logo-384x384.png",
|
||||
"type": "image/png",
|
||||
"sizes": "384x384"
|
||||
},
|
||||
{
|
||||
"src": "/logo-512x512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "maskable_oki.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user