Compare commits
62 Commits
dev
...
288d771075
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
+24
-1
@@ -6,6 +6,16 @@ SITE_URL=http://localhost:3001
|
|||||||
NEXT_PUBLIC_READ_TOKEN=
|
NEXT_PUBLIC_READ_TOKEN=
|
||||||
NEXT_PUBLIC_ADMIN_JWT_SECRET=
|
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
|
# FUNKWHALE VARIABLE
|
||||||
NEXT_PUBLIC_OKI_MIZIK_URL=https://funkwhale-server.com
|
NEXT_PUBLIC_OKI_MIZIK_URL=https://funkwhale-server.com
|
||||||
NEXT_PUBLIC_MIZIK_API_USER=user
|
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_AWTIS_POU_CHAK_PAJ=6
|
||||||
NEXT_PUBLIC_SITE_URL=$SITE_URL
|
NEXT_PUBLIC_SITE_URL=$SITE_URL
|
||||||
|
|
||||||
# NEXT AUTH
|
# Auth
|
||||||
|
|
||||||
NEXTAUTH_URL=http://localhost:3000
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
BETTER_AUTH_URL=http://localhost:3000
|
||||||
|
BETTER_AUTH_SECRET=
|
||||||
|
|
||||||
# TWITTER VARIABLE
|
# TWITTER VARIABLE
|
||||||
|
|
||||||
@@ -32,6 +44,12 @@ GOOGLE_CLIENT_SECRET=
|
|||||||
|
|
||||||
NEXT_PUBLIC_CGU_DOWNLOAD_LINK=
|
NEXT_PUBLIC_CGU_DOWNLOAD_LINK=
|
||||||
|
|
||||||
|
# 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
|
# Menu
|
||||||
|
|
||||||
NEXT_PUBLIC_PROD_DOMAIN=
|
NEXT_PUBLIC_PROD_DOMAIN=
|
||||||
@@ -61,7 +79,9 @@ NEXT_PUBLIC_GADE_USERNAME=
|
|||||||
NEXT_PUBLIC_YOUTUBE_USERNAME=
|
NEXT_PUBLIC_YOUTUBE_USERNAME=
|
||||||
NEXT_PUBLIC_TELEGRAM_GROUP=
|
NEXT_PUBLIC_TELEGRAM_GROUP=
|
||||||
NEXT_PUBLIC_XMPP=
|
NEXT_PUBLIC_XMPP=
|
||||||
|
NEXT_PUBLIC_GIT=
|
||||||
NEXT_PUBLIC_CODEBERG=
|
NEXT_PUBLIC_CODEBERG=
|
||||||
|
NEXT_PUBLIC_BLUESKY_URL=
|
||||||
|
|
||||||
# DOMAIN IMAGE
|
# DOMAIN IMAGE
|
||||||
NEXT_PUBLIC_DOMAINS_IMAGE="localhost:1337 strapi.mondomaine.com"
|
NEXT_PUBLIC_DOMAINS_IMAGE="localhost:1337 strapi.mondomaine.com"
|
||||||
@@ -85,8 +105,11 @@ SMTP_PASSWORD=
|
|||||||
SMTP_FROM=
|
SMTP_FROM=
|
||||||
SMTP_REPLY_TO=
|
SMTP_REPLY_TO=
|
||||||
SMTP_SEND_TO=
|
SMTP_SEND_TO=
|
||||||
|
SMTP_BCC=
|
||||||
|
SMTP_SECURE=
|
||||||
|
|
||||||
# Twitter Authentification
|
# Twitter Authentification
|
||||||
|
|
||||||
NEXT_PUBLIC_TWITTER_API_KEY=
|
NEXT_PUBLIC_TWITTER_API_KEY=
|
||||||
NEXT_PUBLIC_TWITTER_API_KEY_SECRET=
|
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
|
run-name: ${{ gitea.actor }} déploie FRONT BETA
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
@@ -36,7 +34,8 @@ jobs:
|
|||||||
export NVM_DIR="$HOME/.nvm"
|
export NVM_DIR="$HOME/.nvm"
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||||
cd ${{ secrets.FRONT_DEPLOY_PATH }}
|
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
|
corepack enable
|
||||||
yarn install --frozen-lockfile
|
yarn install --frozen-lockfile
|
||||||
yarn build
|
yarn build
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
## Prérequis
|
## Prérequis
|
||||||
- Node >= 20
|
- 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
|
## Variables d'environement
|
||||||
- Copier le contenu du fichier `.env.sample` dans un nouveau fichier `.env`
|
- 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 {slug} = params
|
||||||
const anAwtis = await jwennAwtis(slug)
|
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 description = `${anAwtis.alias}${anAwtis?.biographie ? ` : ${anAwtis?.biographie.slice(0, 100)}...` : ''}`
|
||||||
const url = `${siteUrl}/awtis/${slug}`
|
const url = `${siteUrl}/awtis/${slug}`
|
||||||
|
|
||||||
@@ -50,11 +50,11 @@ export async function generateMetadata(props) {
|
|||||||
type: 'website'
|
type: 'website'
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
site: '@OrganisationKA',
|
site: `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`,
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
creator: '@OrganisationKA',
|
creator: `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`,
|
||||||
images: {
|
images: {
|
||||||
url: `${apiUrl}${kuvetiFormat?.url}`,
|
url: `${apiUrl}${kuvetiFormat?.url}`,
|
||||||
alt: `Photo de ${anAwtis.alias}`,
|
alt: `Photo de ${anAwtis.alias}`,
|
||||||
|
|||||||
+14
-10
@@ -10,17 +10,21 @@ import Pajinasyon from '../../components/awtis/pajinasyon'
|
|||||||
import {jwennAwtisPajinasyon} from '../../lib/oki-api'
|
import {jwennAwtisPajinasyon} from '../../lib/oki-api'
|
||||||
import Footer from '../../components/footer'
|
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 = {
|
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.',
|
description: 'Liste des artistes ayant une ou plusieurs œuvres présentes sur le site.',
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'OKI | Awtis - Liste des artistes',
|
title: `${siteName} | Artistes`,
|
||||||
description: 'Liste des artistes ayant une ou plusieurs œuvres présentes sur le site.',
|
description: 'Liste des artistes ayant une ou plusieurs œuvres présentes sur le site.',
|
||||||
url: 'https://pawol.nu/sipote',
|
url: `${siteUrl}/awtis`,
|
||||||
siteName: 'PAWÒL-NU. Paroles et traductions.',
|
siteName,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: 'https://pawol.nu/logo-512x512.png',
|
url: `${siteUrl}/logo-512x512.png`,
|
||||||
width: 512,
|
width: 512,
|
||||||
height: 512
|
height: 512
|
||||||
}
|
}
|
||||||
@@ -29,14 +33,14 @@ export const metadata = {
|
|||||||
type: 'website'
|
type: 'website'
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
site: '@OrganisationKA',
|
site: twitterHandle,
|
||||||
card: 'summary_large_image',
|
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.',
|
description: 'Liste des artistes ayant une ou plusieurs œuvres présentes sur le site.',
|
||||||
creator: '@OrganisationKA',
|
creator: twitterHandle,
|
||||||
images: {
|
images: {
|
||||||
url: 'https://pawol.nu/logo-512x512.png',
|
url: `${siteUrl}/logo-512x512.png`,
|
||||||
alt: 'OKI Logo',
|
alt: `${siteName} Logo`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-28
@@ -1,25 +1,35 @@
|
|||||||
|
import PlausibleProvider from 'next-plausible'
|
||||||
import TopLoader from '../components/top-loader'
|
import TopLoader from '../components/top-loader'
|
||||||
import Navigasyon from '../components/navigasyon'
|
import Navigasyon from '../components/navigasyon'
|
||||||
import ThemeRegistry from './theme-registy'
|
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 = {
|
export const metadata = {
|
||||||
metadataBase: new URL('https://pawol.nu'),
|
metadataBase: new URL(siteUrl),
|
||||||
manifest: '/manifest.json',
|
manifest: '/manifest.webmanifest',
|
||||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
title: siteName,
|
||||||
description: 'PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.',
|
description: siteDescription,
|
||||||
author: 'OKI',
|
author: orgName,
|
||||||
category: 'music',
|
category: 'music',
|
||||||
creator: 'OKI',
|
creator: orgName,
|
||||||
publisher: 'OKI',
|
publisher: orgName,
|
||||||
applicationName: 'PAWÒL-NU. Paroles et traductions.',
|
applicationName: siteName,
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
title: siteName,
|
||||||
description: 'PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.',
|
description: siteDescription,
|
||||||
url: 'https://pawol.nu',
|
url: siteUrl,
|
||||||
siteName: 'PAWÒL-NU. Paroles et traductions.',
|
siteName,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: 'https://pawol.nu/logo-512x512.png',
|
url: `${siteUrl}/logo-512x512.png`,
|
||||||
width: 512,
|
width: 512,
|
||||||
height: 512
|
height: 512
|
||||||
}
|
}
|
||||||
@@ -28,14 +38,14 @@ export const metadata = {
|
|||||||
type: 'website'
|
type: 'website'
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
site: '@OrganisationKA',
|
site: twitterHandle,
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
title: siteName,
|
||||||
description: 'PAWÒL-NU a pour but de promouvoir le Medukam (ou Wanni Wannan) et les productions afro-diasporiques.',
|
description: siteDescription,
|
||||||
creator: '@OrganisationKA',
|
creator: twitterHandle,
|
||||||
images: {
|
images: {
|
||||||
url: 'https://pawol.nu/logo-512x512.png',
|
url: `${siteUrl}/logo-512x512.png`,
|
||||||
alt: 'PAWÒL-NU Logo',
|
alt: `${siteName} Logo`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,17 +53,16 @@ export const metadata = {
|
|||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'Organization',
|
'@type': 'Organization',
|
||||||
url: 'https://pawol.nu',
|
url: siteUrl,
|
||||||
email: 'kontak@o-k-i.net',
|
email: orgEmail,
|
||||||
keywords: ['OKI', 'PAWÒL-NU', 'Paroles', 'Pawol', 'Medukam', 'Wanni Wannan'],
|
keywords: [orgName, siteName, 'Paroles', 'Pawol'],
|
||||||
legalName: 'PAWÒL-NU',
|
legalName: siteName,
|
||||||
location: 'Guadeloupe'
|
location: orgLocation,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function RootLayout({children, Session}) {
|
export default async function RootLayout({children}) {
|
||||||
return (
|
const inner = (
|
||||||
<html lang='fr' suppressHydrationWarning>
|
<>
|
||||||
<body>
|
|
||||||
<TopLoader color='#ffeb3b' />
|
<TopLoader color='#ffeb3b' />
|
||||||
<ThemeRegistry>
|
<ThemeRegistry>
|
||||||
<Navigasyon />
|
<Navigasyon />
|
||||||
@@ -65,6 +74,15 @@ export default async function RootLayout({children, Session}) {
|
|||||||
dangerouslySetInnerHTML={{__html: JSON.stringify(jsonLd)}}
|
dangerouslySetInnerHTML={{__html: JSON.stringify(jsonLd)}}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang='fr' suppressHydrationWarning>
|
||||||
|
<body>
|
||||||
|
{plausibleUrl
|
||||||
|
? <PlausibleProvider src={plausibleUrl}>{inner}</PlausibleProvider>
|
||||||
|
: inner}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 Box from '@mui/material/Box'
|
||||||
import Container from '@mui/material/Container'
|
import Container from '@mui/material/Container'
|
||||||
import {notFound} from 'next/navigation'
|
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 Statistik from '../components/akey/statistik'
|
||||||
import Akey from '../components/akey'
|
import Akey from '../components/akey'
|
||||||
|
import AnVedette from '../components/akey/an-vedette'
|
||||||
|
|
||||||
import okiLogo from '../public/logo-512x512.png'
|
import okiLogo from '../public/logo-512x512.png'
|
||||||
import Footer from '../components/footer'
|
import Footer from '../components/footer'
|
||||||
import Aso from '../components/akey/aso'
|
import Aso from '../components/akey/aso'
|
||||||
|
|
||||||
async function jwennDone() {
|
async function jwennDone() {
|
||||||
const statistik = await jwennStats()
|
const [statistik, denyeTeks, anVedette] = await Promise.all([jwennStats(), jwennDenyeTeks(), jwennAnVedette()])
|
||||||
|
|
||||||
if (!statistik) {
|
if (!statistik) {
|
||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
return statistik
|
return {statistik, dernierTeks: anVedette ?? denyeTeks?.[0]}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const statistik = await jwennDone()
|
const {statistik, dernierTeks} = await jwennDone()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{display: 'flex', flexDirection: 'column', minHeight: '100vh'}}>
|
<Box sx={{display: 'flex', flexDirection: 'column', minHeight: '100vh'}}>
|
||||||
<Akey logo={okiLogo} />
|
<Akey logo={okiLogo} />
|
||||||
<Container sx={{flexGrow: 100}}>
|
<Container sx={{flexGrow: 100}}>
|
||||||
|
{dernierTeks && <AnVedette teks={dernierTeks} />}
|
||||||
<Statistik statistik={statistik} />
|
<Statistik statistik={statistik} />
|
||||||
<Aso />
|
<Aso />
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function generateMetadata(props) {
|
|||||||
const anTeks = await jwennAnTeks(slug)
|
const anTeks = await jwennAnTeks(slug)
|
||||||
|
|
||||||
const awtis = anTeks?.artistes?.length === 1 ? anTeks?.artistes[0].alias : getAlias(anTeks.artistes, anTeks.prioriteArtistes)
|
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 description = `Paroles de « ${anTeks?.titre} » : ${anTeks?.transcription.slice(0, 100)}...`
|
||||||
const url = `${siteUrl}/paroles/${slug}`
|
const url = `${siteUrl}/paroles/${slug}`
|
||||||
|
|
||||||
@@ -53,11 +53,11 @@ export async function generateMetadata(props) {
|
|||||||
type: 'website'
|
type: 'website'
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
site: '@OrganisationKA',
|
site: `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`,
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
creator: '@OrganisationKA',
|
creator: `@${process.env.NEXT_PUBLIC_TWITTER_USERNAME || 'OrganisationKA'}`,
|
||||||
images: {
|
images: {
|
||||||
url: `${apiUrl}${kuvetiFormat?.url}`,
|
url: `${apiUrl}${kuvetiFormat?.url}`,
|
||||||
alt: `Couverture ${title}`,
|
alt: `Couverture ${title}`,
|
||||||
@@ -80,8 +80,12 @@ export default async function AnPawolPaj(props) {
|
|||||||
'@id': anTeks.musicBrainzUrl || undefined,
|
'@id': anTeks.musicBrainzUrl || undefined,
|
||||||
name: anTeks.titre,
|
name: anTeks.titre,
|
||||||
url: `${siteUrl}/paroles/${slug}`,
|
url: `${siteUrl}/paroles/${slug}`,
|
||||||
|
inLanguage: anTeks.langueSource ?? 'ka',
|
||||||
image: teksKuvetiFormat?.url ? `${apiUrl}${teksKuvetiFormat?.url}` : undefined,
|
image: teksKuvetiFormat?.url ? `${apiUrl}${teksKuvetiFormat?.url}` : undefined,
|
||||||
thumbnailUrl: couverture?.formats?.thumbnail?.url ? `${apiUrl}${couverture.formats.thumbnail.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}) => {
|
byArtist: anTeks.artistes.map(({photo, musicBrainzUrl, alias, slug}) => {
|
||||||
const kuvetiFormat = formatKuveti(photo)
|
const kuvetiFormat = formatKuveti(photo)
|
||||||
|
|
||||||
@@ -93,7 +97,18 @@ export default async function AnPawolPaj(props) {
|
|||||||
image: kuvetiFormat?.url ? `${apiUrl}${kuvetiFormat?.url}` : undefined
|
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 (
|
return (
|
||||||
|
|||||||
+14
-10
@@ -4,17 +4,21 @@ import {jwennTeks} from '../../lib/oki-api'
|
|||||||
import TeksDrawer from '../../components/teks/teks-drawer'
|
import TeksDrawer from '../../components/teks/teks-drawer'
|
||||||
import Loading from './loading'
|
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 = {
|
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.',
|
description: 'Retrouvez les paroles et les traductions de vos chansons préférées.',
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'PAWÒL-NU. Paroles et traductions.',
|
title: siteName,
|
||||||
description: 'Retrouvez les paroles et les traductions de vos chansons préférées.',
|
description: 'Retrouvez les paroles et les traductions de vos chansons préférées.',
|
||||||
url: 'https://pawol.nu/paroles',
|
url: `${siteUrl}/paroles`,
|
||||||
siteName: 'PAWÒL-NU. Paroles et traductions.',
|
siteName,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: 'https://pawol.nu/logo-512x512.png',
|
url: `${siteUrl}/logo-512x512.png`,
|
||||||
width: 512,
|
width: 512,
|
||||||
height: 512
|
height: 512
|
||||||
}
|
}
|
||||||
@@ -23,14 +27,14 @@ export const metadata = {
|
|||||||
type: 'website'
|
type: 'website'
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
site: '@OrganisationKA',
|
site: twitterHandle,
|
||||||
card: 'summary_large_image',
|
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.',
|
description: 'Retrouvez les paroles et les traductions de vos chansons préférées.',
|
||||||
creator: '@OrganisationKA',
|
creator: twitterHandle,
|
||||||
images: {
|
images: {
|
||||||
url: 'https://pawol.nu/logo-512x512.png',
|
url: `${siteUrl}/logo-512x512.png`,
|
||||||
alt: 'OKI Logo',
|
alt: `${siteName} Logo`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,15 @@ import Box from '@mui/material/Box'
|
|||||||
import {notFound} from 'next/navigation'
|
import {notFound} from 'next/navigation'
|
||||||
|
|
||||||
import {jwennDenyeTeks} from '../../lib/oki-api'
|
import {jwennDenyeTeks} from '../../lib/oki-api'
|
||||||
|
import {formatKuveti} from '../../lib/kuveti'
|
||||||
import DenyeTeks from '../../components/teks/denye-teks'
|
import DenyeTeks from '../../components/teks/denye-teks'
|
||||||
import Footer from '../../components/footer'
|
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() {
|
async function jwennDone() {
|
||||||
const denyeTeks = await jwennDenyeTeks()
|
const denyeTeks = await jwennDenyeTeks()
|
||||||
|
|
||||||
@@ -15,6 +21,38 @@ async function jwennDone() {
|
|||||||
return denyeTeks
|
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() {
|
export default async function PawolPaj() {
|
||||||
const denyeTeks = await jwennDone()
|
const denyeTeks = await jwennDone()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -15,6 +15,6 @@ export default function robots() {
|
|||||||
userAgent: '*',
|
userAgent: '*',
|
||||||
allow: '/',
|
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 = {
|
export const metadata = {
|
||||||
title: 'PAWÒL-NU | Soutenir ORGANISATION KA INTERNATIONALE !',
|
title: sipoteTitle,
|
||||||
description: 'Vous pouvez nous soutenir via Liberapay ou PayPal',
|
description: 'Vous pouvez nous soutenir via Liberapay ou PayPal',
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'PAWÒL-NU | Soutenir ORGANISATION KA INTERNATIONALE !',
|
title: sipoteTitle,
|
||||||
description: 'Vous pouvez nous soutenir via Liberapay ou PayPal.',
|
description: 'Vous pouvez nous soutenir via Liberapay ou PayPal.',
|
||||||
url: 'https://pawol.nu/sipote',
|
url: `${siteUrl}/sipote`,
|
||||||
siteName: 'PAWÒL-NU | Paroles et traductions.',
|
siteName,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: 'https://pawol.nu/sipote.png',
|
url: `${siteUrl}/sipote.png`,
|
||||||
width: 500,
|
width: 500,
|
||||||
height: 500
|
height: 500
|
||||||
}
|
}
|
||||||
@@ -17,14 +23,14 @@ export const metadata = {
|
|||||||
type: 'website'
|
type: 'website'
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
site: '@OrganisationKA',
|
site: twitterHandle,
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'PAWÒL-NU | Soutenir ORGANISATION KA INTERNATIONALE !',
|
title: sipoteTitle,
|
||||||
description: 'Vous pouvez nous soutenir via Liberapay ou PayPal.',
|
description: 'Vous pouvez nous soutenir via Liberapay ou PayPal.',
|
||||||
creator: '@OrganisationKA',
|
creator: twitterHandle,
|
||||||
images: {
|
images: {
|
||||||
url: 'https://pawol.nu/sipote.png',
|
url: `${siteUrl}/sipote.png`,
|
||||||
alt: 'Sipòte OKI',
|
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
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import {useState} from 'react'
|
import {useState} from 'react'
|
||||||
|
import Image from 'next/image'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Accordion from '@mui/material/Accordion'
|
import Accordion from '@mui/material/Accordion'
|
||||||
@@ -15,52 +16,91 @@ import Grid from '@mui/material/Grid'
|
|||||||
import Paper from '@mui/material/Paper'
|
import Paper from '@mui/material/Paper'
|
||||||
import Card from '@mui/material/Card'
|
import Card from '@mui/material/Card'
|
||||||
import CardContent from '@mui/material/CardContent'
|
import CardContent from '@mui/material/CardContent'
|
||||||
import Avatar from '@mui/material/Avatar'
|
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import {green} from '@mui/material/colors'
|
import {green} from '@mui/material/colors'
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||||
import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace'
|
import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace'
|
||||||
|
import VerifiedIcon from '@mui/icons-material/Verified'
|
||||||
|
|
||||||
import {formatKuveti} from '../../lib/kuveti'
|
import {formatKuveti} from '../../lib/kuveti'
|
||||||
|
import {StreamButton} from '../streaming-buttons'
|
||||||
import AwtisBiyografi from './awtis-biyografi'
|
import AwtisBiyografi from './awtis-biyografi'
|
||||||
import MizikLyen from './mizik-lyen'
|
import MizikLyen from './mizik-lyen'
|
||||||
|
|
||||||
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
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'}))
|
const sortTeks = paroles => paroles.sort((a, b) => a.titre.localeCompare(b.titre, 'fr', {sensitivity: 'base'}))
|
||||||
|
|
||||||
export default function AwtisDetay({anAwtis}) {
|
export default function AwtisDetay({anAwtis}) {
|
||||||
const [esByografiOuve, meteEsByografiOuve] = useState(false)
|
const [esByografiOuve, meteEsByografiOuve] = useState(false)
|
||||||
const {alias, biographie, paroles, photo} = anAwtis
|
const {alias, biographie, paroles, photo, isExclusiveArtist, titrePhare} = anAwtis
|
||||||
const sortedTeks = sortTeks(paroles)
|
const sortedTeks = sortTeks(paroles)
|
||||||
const gwanBiyo = biographie && biographie.length > 100
|
const gwanBiyo = biographie && biographie.length > 100
|
||||||
|
|
||||||
const biyo = gwanBiyo ? `${biographie.slice(0, 100)}...` : biographie
|
const biyo = gwanBiyo ? `${biographie.slice(0, 100)}...` : biographie
|
||||||
|
|
||||||
const handleClick = () => {
|
const hasStreaming = isExclusiveArtist && titrePhare?.streamAudio?.length > 0
|
||||||
meteEsByografiOuve(true)
|
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 (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Box sx={{marginTop: 8, marginBottom: 2}}>
|
<Box sx={{mt: 8, mb: 2}}>
|
||||||
<Typography sx={{textAlign: 'center'}} variant='h6' component='h1'>
|
<Typography sx={{textAlign: 'center'}} variant='h6' component='h1'>
|
||||||
{alias}
|
{alias}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{justifyContent: 'center', display: 'flex', marginBottom: 2}}>
|
|
||||||
<Avatar
|
<Box sx={{display: 'flex', justifyContent: 'center', mb: 2}}>
|
||||||
src={`${photo?.url ? `${IMAGE_URL}${photo?.url}` : noImageUrl}`}
|
<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}`}
|
alt={`Photo ${alias}`}
|
||||||
sx={{width: 200, height: 200, border: `2px solid ${green[500]}`}}
|
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}>
|
</Box>
|
||||||
|
|
||||||
|
{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 && (
|
{biyo && (
|
||||||
<Grid size={{xs: 12, md: 6}}>
|
<Grid size={{xs: 12, md: 6}}>
|
||||||
<Card sx={{minWidth: 300}}>
|
<Card sx={{minWidth: 300}}>
|
||||||
<CardActionArea onClick={handleClick}>
|
<CardActionArea onClick={() => meteEsByografiOuve(true)}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography gutterBottom variant='body1' component='h2'>
|
<Typography gutterBottom variant='body1' component='h2'>
|
||||||
<strong>Biographie</strong>
|
<strong>Biographie</strong>
|
||||||
@@ -73,6 +113,44 @@ export default function AwtisDetay({anAwtis}) {
|
|||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</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}}>
|
<Grid size={{xs: 12, md: 6}}>
|
||||||
<Box marginbottom={3}>
|
<Box marginbottom={3}>
|
||||||
{paroles.length > 1 ? (
|
{paroles.length > 1 ? (
|
||||||
@@ -82,14 +160,13 @@ export default function AwtisDetay({anAwtis}) {
|
|||||||
aria-controls='panel-teks-content'
|
aria-controls='panel-teks-content'
|
||||||
id='panel-teks-header'
|
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' />
|
<Chip color='primary' label={paroles.length} size='small' variant='contained' />
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails sx={{paddingInline: 0}}>
|
<AccordionDetails sx={{paddingInline: 0}}>
|
||||||
{sortedTeks.map(anPawol => {
|
{sortedTeks.map(anPawol => {
|
||||||
const {couverture} = anPawol
|
const {couverture} = anPawol
|
||||||
const kuvetiFormat = formatKuveti(couverture)
|
const kuvetiFormat = couverture?.formats?.thumbnail || formatKuveti(couverture)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={anPawol.id} sx={{paddingBlock: 0.5}}>
|
<Box key={anPawol.id} sx={{paddingBlock: 0.5}}>
|
||||||
<MizikLyen anPawol={anPawol} kuveti={kuvetiFormat} />
|
<MizikLyen anPawol={anPawol} kuveti={kuvetiFormat} />
|
||||||
@@ -105,7 +182,7 @@ export default function AwtisDetay({anAwtis}) {
|
|||||||
<Box>
|
<Box>
|
||||||
<Typography gutterBottom textalign='center' variant='body1' component='h2'><strong>Parole</strong></Typography>
|
<Typography gutterBottom textalign='center' variant='body1' component='h2'><strong>Parole</strong></Typography>
|
||||||
<Paper sx={{height: '100%', paddingBlock: 2}}>
|
<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>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
@@ -113,13 +190,15 @@ export default function AwtisDetay({anAwtis}) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box sx={{textAlign: 'center', marginBlock: 3}} >
|
|
||||||
|
<Box sx={{textAlign: 'center', marginBlock: 3}}>
|
||||||
<Link passHref href='/awtis'>
|
<Link passHref href='/awtis'>
|
||||||
<Button variant='outlined' startIcon={<KeyboardBackspaceIcon />}>
|
<Button variant='outlined' startIcon={<KeyboardBackspaceIcon />}>
|
||||||
Retour aux artistes
|
Retour aux artistes
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{esByografiOuve && (
|
{esByografiOuve && (
|
||||||
<AwtisBiyografi
|
<AwtisBiyografi
|
||||||
alias={alias}
|
alias={alias}
|
||||||
|
|||||||
@@ -10,14 +10,17 @@ import Card from '@mui/material/Card'
|
|||||||
import CardMedia from '@mui/material/CardMedia'
|
import CardMedia from '@mui/material/CardMedia'
|
||||||
import CardContent from '@mui/material/CardContent'
|
import CardContent from '@mui/material/CardContent'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
|
import Chip from '@mui/material/Chip'
|
||||||
|
|
||||||
import {styled} from '@mui/material/styles'
|
import {styled} from '@mui/material/styles'
|
||||||
|
import VerifiedIcon from '@mui/icons-material/Verified'
|
||||||
|
|
||||||
import AwtisBiyografi from './awtis-biyografi'
|
import AwtisBiyografi from './awtis-biyografi'
|
||||||
|
|
||||||
const PREFIX = 'awtis-kat'
|
const PREFIX = 'awtis-kat'
|
||||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3001'
|
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 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 = {
|
const classes = {
|
||||||
root: `${PREFIX}-root`,
|
root: `${PREFIX}-root`,
|
||||||
@@ -55,18 +58,32 @@ export default function AwtisKat({artiste}) {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [esByografiOuve, meteEsByografiOuve] = useState(false)
|
const [esByografiOuve, meteEsByografiOuve] = useState(false)
|
||||||
|
|
||||||
const {alias, biographie, paroles, photo, slug} = artiste
|
const {alias, biographie, paroles, photo, slug, isExclusiveArtist} = artiste
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid size={{xs: 12, sm: 6, md: 4}}>
|
<Grid size={{xs: 12, sm: 6, md: 4}}>
|
||||||
<Kat>
|
<Kat>
|
||||||
<Card sx={{maxWidth: 340}}>
|
<Card sx={{maxWidth: 340, position: 'relative', ...(isExclusiveArtist && {outline: '2px solid #FFD700'})}}>
|
||||||
<CardActionArea onClick={() => router.push(`${SITE_URL}/awtis/${slug}`)}>
|
{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
|
<CardMedia
|
||||||
className={classes.media}
|
className={classes.media}
|
||||||
component='img'
|
component='img'
|
||||||
alt={alias}
|
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}
|
title={alias}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default function ChecheAwtis() {
|
|||||||
<Avatar
|
<Avatar
|
||||||
style={{ marginRight: 8 }}
|
style={{ marginRight: 8 }}
|
||||||
alt={option?.alias}
|
alt={option?.alias}
|
||||||
src={`${IMAGE_URL}${option?.photo?.formats?.thumbnail?.url}`}
|
src={`${IMAGE_URL}${option?.photo?.formats?.thumbnail?.url || option?.photo?.url || ''}`}
|
||||||
/>
|
/>
|
||||||
{option?.alias}
|
{option?.alias}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function MizikLis({niAwtis, paroles, meteEsMobilOuve}) {
|
|||||||
itemContent={index => {
|
itemContent={index => {
|
||||||
const anPawol = pawol[index]
|
const anPawol = pawol[index]
|
||||||
const {couverture} = anPawol
|
const {couverture} = anPawol
|
||||||
const kuvetiFormat = formatKuveti(couverture)
|
const kuvetiFormat = couverture?.formats?.thumbnail || formatKuveti(couverture)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MizikLyen niAwtis={niAwtis} anPawol={anPawol} kuveti={kuvetiFormat} slug={params.slug} meteEsMobilOuve={meteEsMobilOuve} />
|
<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 DialogActions from '@mui/material/DialogActions'
|
||||||
import DialogContent from '@mui/material/DialogContent'
|
import DialogContent from '@mui/material/DialogContent'
|
||||||
import DialogTitle from '@mui/material/DialogTitle'
|
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 useMediaQuery from '@mui/material/useMediaQuery'
|
||||||
import {useTheme} from '@mui/material/styles'
|
import {useTheme} from '@mui/material/styles'
|
||||||
import LicensesInfo from './licenses-infos'
|
import LicensesInfo from './licenses-infos'
|
||||||
|
|
||||||
export default function LicenseModal({license}) {
|
export default function LicenseModal({license, sourceOriginale, remixes}) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const fullScreen = useMediaQuery(theme.breakpoints.down('md'))
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'))
|
||||||
@@ -53,6 +55,48 @@ export default function LicenseModal({license}) {
|
|||||||
</Box>
|
</Box>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent dividers>
|
<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} />
|
<LicensesInfo license={license} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
@@ -66,5 +110,7 @@ export default function LicenseModal({license}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LicenseModal.propTypes = {
|
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 {styled, useTheme} from '@mui/material/styles'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import {Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography} from '@mui/material'
|
import {Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography} from '@mui/material'
|
||||||
import {useRouter} from 'next/navigation'
|
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||||
import Cgu from '.'
|
import Cgu from '.'
|
||||||
|
|
||||||
@@ -23,17 +22,11 @@ const CGU_DOWNLOAD_LINK = process.env.NEXT_PUBLIC_CGU_DOWNLOAD_LINK
|
|||||||
export default function CGUDialog({open, setOpen}) {
|
export default function CGUDialog({open, setOpen}) {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const fullScreen = useMediaQuery(theme.breakpoints.down('md'))
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'))
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = event => {
|
|
||||||
event.preventDefault()
|
|
||||||
router.push(CGU_DOWNLOAD_LINK)
|
|
||||||
}
|
|
||||||
|
|
||||||
const descriptionElementRef = useRef(null)
|
const descriptionElementRef = useRef(null)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@@ -64,7 +57,24 @@ export default function CGUDialog({open, setOpen}) {
|
|||||||
id='scroll-dialog-description'
|
id='scroll-dialog-description'
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
|
{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 />
|
<Cgu />
|
||||||
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export default function Cgu() {
|
|||||||
<ArrowRightAltIcon />
|
<ArrowRightAltIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>
|
<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>
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
|||||||
+152
-74
@@ -1,9 +1,12 @@
|
|||||||
|
import {useState, useEffect, useRef} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import List from '@mui/material/List'
|
import List from '@mui/material/List'
|
||||||
import ListSubheader from '@mui/material/ListSubheader'
|
import ListSubheader from '@mui/material/ListSubheader'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import Box from '@mui/material/Box'
|
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 Table from '@mui/material/Table'
|
||||||
import TableHead from '@mui/material/TableHead'
|
import TableHead from '@mui/material/TableHead'
|
||||||
import TableBody from '@mui/material/TableBody'
|
import TableBody from '@mui/material/TableBody'
|
||||||
@@ -11,12 +14,12 @@ import TableCell, {tableCellClasses} from '@mui/material/TableCell'
|
|||||||
import TableRow from '@mui/material/TableRow'
|
import TableRow from '@mui/material/TableRow'
|
||||||
import TableContainer from '@mui/material/TableContainer'
|
import TableContainer from '@mui/material/TableContainer'
|
||||||
import Paper from '@mui/material/Paper'
|
import Paper from '@mui/material/Paper'
|
||||||
import FileSaver from 'file-saver'
|
|
||||||
import DescriptionIcon from '@mui/icons-material/Description'
|
import DescriptionIcon from '@mui/icons-material/Description'
|
||||||
import LibraryMusicIcon from '@mui/icons-material/LibraryMusic'
|
import LibraryMusicIcon from '@mui/icons-material/LibraryMusic'
|
||||||
import {Link} from '@mui/material'
|
import {Link} from '@mui/material'
|
||||||
|
|
||||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
||||||
|
const audioMetaCache = {}
|
||||||
|
|
||||||
const StyledTableCell = styled(TableCell)(({theme}) => ({
|
const StyledTableCell = styled(TableCell)(({theme}) => ({
|
||||||
[`&.${tableCellClasses.head}`]: {
|
[`&.${tableCellClasses.head}`]: {
|
||||||
@@ -47,8 +50,10 @@ function formatSize(size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function FilesList({files}) {
|
export default function FilesList({files}) {
|
||||||
const theme = useTheme()
|
|
||||||
const {mode} = useColorScheme()
|
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 musicFiles = files.filter(file => file.mime.startsWith('audio'))
|
||||||
const pdfFiles = files.filter(file => file.mime === 'application/pdf')
|
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()]
|
return extensionOrder[a.ext.toLowerCase()] - extensionOrder[b.ext.toLowerCase()]
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClick = (e, url, fileName) => {
|
useEffect(() => {
|
||||||
e.stopPropagation()
|
const audioFiles = files.filter(f => f.mime.startsWith('audio'))
|
||||||
FileSaver.saveAs(url, fileName)
|
if (audioFiles.length === 0) return
|
||||||
|
|
||||||
|
let cancelled = false
|
||||||
|
|
||||||
|
async function fetchAllMeta() {
|
||||||
|
const mm = await import('music-metadata-browser')
|
||||||
|
const results = {}
|
||||||
|
await Promise.all(
|
||||||
|
audioFiles.map(async file => {
|
||||||
|
if (file.id in audioMetaCache) {
|
||||||
|
results[file.id] = audioMetaCache[file.id]
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const getQuality = (extension, caption) => {
|
try {
|
||||||
switch (extension) {
|
const response = await fetch(`${apiUrl}${file.url}`, {
|
||||||
case '.ogg':
|
headers: {Range: 'bytes=0-32767'},
|
||||||
case '.aac':
|
})
|
||||||
case '.mp3':
|
const buffer = await response.arrayBuffer()
|
||||||
return (
|
const meta = await mm.parseBuffer(new Uint8Array(buffer), {mimeType: file.mime, skipCovers: true})
|
||||||
<Typography sx={{
|
audioMetaCache[file.id] = meta.format
|
||||||
backgroundColor: '#393940',
|
results[file.id] = meta.format
|
||||||
color: '#fff',
|
} catch {
|
||||||
borderRadius: '0.66rem',
|
audioMetaCache[file.id] = null
|
||||||
border: mode === 'dark' ? '1px solid #fff' : 'none',
|
results[file.id] = null
|
||||||
fontSize: '0.75rem',
|
}
|
||||||
letterSpacing: '0.1rem',
|
})
|
||||||
padding: '0.2515rem 0.6707rem',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
fontWeight: 'bold'
|
|
||||||
}}
|
|
||||||
>Faible</Typography>
|
|
||||||
)
|
)
|
||||||
case '.flac':
|
if (!cancelled) setAudioMeta(results)
|
||||||
if (caption === 'MAX') {
|
}
|
||||||
|
|
||||||
|
fetchAllMeta()
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [files])
|
||||||
|
|
||||||
|
const losslessFormats = new Set(['.flac', '.wav', '.aiff', '.alac'])
|
||||||
|
|
||||||
|
const qualityBadge = ext => {
|
||||||
|
const isHaute = losslessFormats.has(ext.toLowerCase())
|
||||||
return (
|
return (
|
||||||
<Typography sx={{
|
<Typography variant='caption' sx={{
|
||||||
backgroundColor: '#332619',
|
backgroundColor: isHaute ? '#07332f' : '#393940',
|
||||||
color: '#ffbe7d',
|
color: isHaute ? '#21feec' : '#fff',
|
||||||
borderRadius: '0.66rem',
|
borderRadius: '0.66rem',
|
||||||
border: mode === 'dark' ? '1px solid #ffbe7d' : 'none',
|
border: mode === 'dark' ? `1px solid ${isHaute ? '#21feec' : '#fff'}` : 'none',
|
||||||
fontSize: '0.75rem',
|
padding: '0.15rem 0.5rem',
|
||||||
letterSpacing: '0.1rem',
|
|
||||||
padding: '0.2515rem 0.6707rem',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
textAlign: 'center'
|
letterSpacing: '0.05rem',
|
||||||
}}
|
textTransform: 'uppercase',
|
||||||
>{caption}</Typography>
|
}}>{isHaute ? 'Haute' : 'Faible'}</Typography>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (caption === 'HAUTE') {
|
const renderMeta = file => {
|
||||||
return (
|
const format = file.ext.replace('.', '').toUpperCase()
|
||||||
<Typography sx={{
|
|
||||||
backgroundColor: '#07332f',
|
if (!(file.id in audioMeta)) {
|
||||||
color: '#21feec',
|
return <Skeleton variant='text' width={80} />
|
||||||
borderRadius: '0.66rem',
|
|
||||||
border: mode === 'dark' ? '1px solid #21feec' : 'none',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
letterSpacing: '0.1rem',
|
|
||||||
padding: '0.2515rem 0.6707rem',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
fontWeight: 'bold'
|
|
||||||
}}
|
|
||||||
>{caption}</Typography>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const 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 (
|
return (
|
||||||
<Typography sx={{
|
<Box display='flex' flexDirection='column' gap={0.5}>
|
||||||
backgroundColor: '#07332f',
|
<Box display='flex' alignItems='center' gap={1}>
|
||||||
color: '#21feec',
|
{qualityBadge(file.ext)}
|
||||||
borderRadius: '0.66rem',
|
<Typography variant='caption' sx={{fontWeight: 'bold', ml: 1}}>{format}</Typography>
|
||||||
border: mode === 'dark' ? '1px solid #21feec' : 'none',
|
</Box>
|
||||||
fontSize: '0.75rem',
|
{details && <Typography variant='caption' sx={{color: 'text.secondary'}}>{details}</Typography>}
|
||||||
letterSpacing: '0.1rem',
|
</Box>
|
||||||
padding: '0.2515rem 0.6707rem',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
fontWeight: 'bold'
|
|
||||||
}}
|
|
||||||
>Haute</Typography>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
default:
|
|
||||||
return <DescriptionIcon sx={{marginRight: 1}} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => () => {
|
||||||
|
Object.values(controllersRef.current).forEach(c => c.abort())
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleClick = async (e, url, fileName, fileId) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (fileId in downloading) return
|
||||||
|
|
||||||
|
const controller = new AbortController()
|
||||||
|
controllersRef.current[fileId] = controller
|
||||||
|
|
||||||
|
setDownloading(prev => ({...prev, [fileId]: 0}))
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {signal: controller.signal})
|
||||||
|
const contentLength = +response.headers.get('content-length')
|
||||||
|
const reader = response.body.getReader()
|
||||||
|
const chunks = []
|
||||||
|
let received = 0
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const {done, value} = await reader.read()
|
||||||
|
if (done) break
|
||||||
|
chunks.push(value)
|
||||||
|
received += value.length
|
||||||
|
if (contentLength) {
|
||||||
|
setDownloading(prev => ({...prev, [fileId]: Math.round(received / contentLength * 100)}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob(chunks, {type: response.headers.get('content-type')})
|
||||||
|
const blobUrl = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = blobUrl
|
||||||
|
a.download = fileName
|
||||||
|
a.click()
|
||||||
|
URL.revokeObjectURL(blobUrl)
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name !== 'AbortError') throw error
|
||||||
|
} finally {
|
||||||
|
delete controllersRef.current[fileId]
|
||||||
|
setDownloading(prev => {
|
||||||
|
const next = {...prev}
|
||||||
|
delete next[fileId]
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = (e, fileId) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
controllersRef.current[fileId]?.abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -151,7 +210,7 @@ export default function FilesList({files}) {
|
|||||||
component='nav'
|
component='nav'
|
||||||
aria-labelledby='nested-list-subheader'
|
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'>
|
<Box paddingBlock={1} display='flex' justifyContent='center'>
|
||||||
<LibraryMusicIcon />
|
<LibraryMusicIcon />
|
||||||
<Typography gutterBottom marginLeft={1} variant='button'>Musiques</Typography>
|
<Typography gutterBottom marginLeft={1} variant='button'>Musiques</Typography>
|
||||||
@@ -159,10 +218,9 @@ export default function FilesList({files}) {
|
|||||||
</ListSubheader>
|
</ListSubheader>
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table size='small' aria-label='Musiques'>
|
<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>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<StyledTableCell align='center'>QUALITÉ</StyledTableCell>
|
<StyledTableCell align='center'>FORMAT</StyledTableCell>
|
||||||
<StyledTableCell />
|
<StyledTableCell />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
@@ -170,19 +228,39 @@ export default function FilesList({files}) {
|
|||||||
{sortedMusicFiles.map(file => (
|
{sortedMusicFiles.map(file => (
|
||||||
<StyledTableRow key={file.id}>
|
<StyledTableRow key={file.id}>
|
||||||
<StyledTableCell>
|
<StyledTableCell>
|
||||||
{getQuality(file.ext.toLowerCase(), file?.caption?.toUpperCase())}
|
{renderMeta(file)}
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
<StyledTableCell align='left'>
|
<StyledTableCell align='left'>
|
||||||
<Link
|
<Link
|
||||||
href='#'
|
href='#'
|
||||||
underline='hover'
|
underline='hover'
|
||||||
sx={{fontWeight: 'bold'}}
|
sx={{fontWeight: 'bold', pointerEvents: file.id in downloading ? 'none' : 'auto'}}
|
||||||
aria-label='download'
|
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}
|
{file.name}
|
||||||
</Link>
|
</Link>
|
||||||
<small style={{marginLeft: 3}}>({formatSize(file.size)})</small>
|
<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>
|
</StyledTableCell>
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
))}
|
))}
|
||||||
@@ -198,7 +276,7 @@ export default function FilesList({files}) {
|
|||||||
component='nav'
|
component='nav'
|
||||||
aria-labelledby='nested-list-subheader'
|
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'>
|
<Box paddingBlock={1} display='flex' justifyContent='center' alignSelf='center'>
|
||||||
<DescriptionIcon />
|
<DescriptionIcon />
|
||||||
<Typography gutterBottom marginLeft={1} variant='button'>Paroles</Typography>
|
<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 siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'
|
||||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
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({
|
export default function HeadLayout({
|
||||||
children,
|
children,
|
||||||
@@ -20,38 +23,38 @@ export default function HeadLayout({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Head prefix='website: https://ogp.me/ns/website#'>
|
<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='canonical' href={`${slug ? `${siteUrl}/${slug}` : siteUrl}`} />
|
||||||
<link rel='manifest' href='/manifest.json' />
|
<link rel='manifest' href='/manifest.json' />
|
||||||
<link rel='icon' type='image/x-icon' sizes='32x32' href='/favicon.ico' />
|
<link rel='icon' type='image/x-icon' sizes='32x32' href='/favicon.ico' />
|
||||||
<link rel='apple-touch-icon' href='/favicon.ico' />
|
<link rel='apple-touch-icon' href='/favicon.ico' />
|
||||||
<meta name='monetization' content='$ilp.uphold.com/q7MFmYWNpwnr' />
|
<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:card' content='summary_large_image' />
|
||||||
<meta name='twitter:url' content={`${slug ? `${siteUrl}/${slug}` : siteUrl}`} />
|
<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:title' content={`${title ? title : siteName}`} />
|
||||||
<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: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:image' content={`${imageUrl ? `${apiUrl}${imageUrl}` : `${siteUrl}/logo-192x192.png`}`} />
|
||||||
<meta name='twitter:creator' content='@OrganisationKA' />
|
<meta name='twitter:creator' content={twitterHandle} />
|
||||||
<meta name='twitter:site' content='@OrganisationKA' />
|
<meta name='twitter:site' content={twitterHandle} />
|
||||||
<meta name='theme-color' content='#303030' />
|
<meta name='theme-color' content='#303030' />
|
||||||
<meta name='apple-mobile-web-app-status-bar' content='#303030' />
|
<meta name='apple-mobile-web-app-status-bar' content='#303030' />
|
||||||
<meta charSet='utf-8' />
|
<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='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='OKI' />
|
<meta name='author' content={orgName} />
|
||||||
<meta name='viewport' content='minimum-scale=1, initial-scale=1, width=device-width' />
|
<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:url' content={`${slug ? `${siteUrl}/${slug}` : siteUrl}`} />
|
||||||
<meta property='og:type' content='website' />
|
<meta property='og:type' content='website' />
|
||||||
<meta property='og:site_name' content={`${title ? title : 'PAWÒL-NU. Paroles et traductions.'}`} />
|
<meta property='og:site_name' content={`${title ? title : siteName}`} />
|
||||||
<meta property='og:title' content={`${title ? title : 'PAWÒL-NU. Paroles et traductions.'}`} />
|
<meta property='og:title' content={`${title ? title : siteName}`} />
|
||||||
<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: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:locale' content='fr_FR' />
|
||||||
<meta property='og:image' content={`${imageUrl ? `${apiUrl}${imageUrl}` : `${siteUrl}/logo-512x512.png`}`} />
|
<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:secure_url' content={`${imageUrl ? `${apiUrl}${imageUrl}` : `${siteUrl}/logo-512x512.png`}`} />
|
||||||
<meta property='og:image:type' content={imageMime ? imageMime : 'image/png'} />
|
<meta property='og:image:type' content={imageMime ? imageMime : 'image/png'} />
|
||||||
<meta property='og:image:width' content={imageWidth ? imageWidth : '512'} />
|
<meta property='og:image:width' content={imageWidth ? imageWidth : '512'} />
|
||||||
<meta property='og:image:height' content={imageHeight ? imageHeight : '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>
|
</Head>
|
||||||
<Navigasyon selectedTab={tab} />
|
<Navigasyon selectedTab={tab} />
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default function RezoDialog() {
|
|||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
>
|
>
|
||||||
<BootstrapDialogTitle id='rézo-dialog' 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>
|
</BootstrapDialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ function AjouteTradiksyon({showSwitch, disableSwitch, tradiksyonOtomatik, setTra
|
|||||||
aria-haspopup='true'
|
aria-haspopup='true'
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
Ajouter une traduction <br /> 🇫🇷 🇬🇧 🇪🇸 🇩🇪 🇮🇹
|
Ajouter une traduction <br /> 🇺🇸 🇫🇷 🇪🇸 🇩🇪 🇮🇹
|
||||||
</Button>
|
</Button>
|
||||||
<StyledMenu
|
<StyledMenu
|
||||||
keepMounted
|
keepMounted
|
||||||
@@ -88,6 +88,13 @@ function AjouteTradiksyon({showSwitch, disableSwitch, tradiksyonOtomatik, setTra
|
|||||||
}}
|
}}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
>
|
>
|
||||||
|
<StyledMenuItem
|
||||||
|
id='en'
|
||||||
|
classes={{
|
||||||
|
root: classes.root
|
||||||
|
}}
|
||||||
|
onClick={handleClose}
|
||||||
|
>🇺🇸 Anglais (US)</StyledMenuItem>
|
||||||
<StyledMenuItem
|
<StyledMenuItem
|
||||||
id='fr'
|
id='fr'
|
||||||
classes={{
|
classes={{
|
||||||
@@ -95,13 +102,6 @@ function AjouteTradiksyon({showSwitch, disableSwitch, tradiksyonOtomatik, setTra
|
|||||||
}}
|
}}
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>🇫🇷 Français</StyledMenuItem>
|
>🇫🇷 Français</StyledMenuItem>
|
||||||
<StyledMenuItem
|
|
||||||
id='en'
|
|
||||||
classes={{
|
|
||||||
root: classes.root
|
|
||||||
}}
|
|
||||||
onClick={handleClose}
|
|
||||||
>🇬🇧 Anglais</StyledMenuItem>
|
|
||||||
<StyledMenuItem
|
<StyledMenuItem
|
||||||
id='es'
|
id='es'
|
||||||
classes={{
|
classes={{
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default function Presantasyon() {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
<Typography paragraph='true' variant='subtitle1' component='div'>
|
<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>
|
||||||
<Typography paragraph='true' variant='subtitle1' component='div'>
|
<Typography paragraph='true' variant='subtitle1' component='div'>
|
||||||
<strong>Merci par avance pour votre soutien 🥰</strong>
|
<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 {useState, useEffect, useRef} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 Box from '@mui/material/Box'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import Slider from '@mui/material/Slider'
|
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'
|
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}) => ({
|
const Widget = styled('div')(({theme}) => ({
|
||||||
padding: 16,
|
padding: 16,
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
@@ -59,11 +71,13 @@ export default function Lekte({audio, url, parole}) {
|
|||||||
const audioRef = useRef(new Audio(audio))
|
const audioRef = useRef(new Audio(audio))
|
||||||
const intervalRef = useRef()
|
const intervalRef = useRef()
|
||||||
const isReady = useRef(false)
|
const isReady = useRef(false)
|
||||||
const {duration} = audioRef.current
|
const [duration, setDuration] = useState(0)
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
const {mode} = useColorScheme()
|
||||||
const [position, setPosition] = useState(0)
|
const [position, setPosition] = useState(0)
|
||||||
const [volume, setVolume] = useState(100)
|
const [volume, setVolume] = useState(100)
|
||||||
const [isPlaying, setIsPlaying] = useState(false)
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
|
const [lrcLines, setLrcLines] = useState(null)
|
||||||
const alias = getAlias(parole.artistes, parole.prioriteArtistes)
|
const alias = getAlias(parole.artistes, parole.prioriteArtistes)
|
||||||
|
|
||||||
function formatDuration(value) {
|
function formatDuration(value) {
|
||||||
@@ -76,8 +90,8 @@ export default function Lekte({audio, url, parole}) {
|
|||||||
return `${minute}:${secondLeft <= 9 ? `0${secondLeft}` : secondLeft}`
|
return `${minute}:${secondLeft <= 9 ? `0${secondLeft}` : secondLeft}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainIconColor = theme.palette.mode === 'dark' ? '#fff' : '#000'
|
const mainIconColor = mode === 'dark' ? '#fff' : '#000'
|
||||||
const lightIconColor = theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.4)' : grey[900]
|
const lightIconColor = mode === 'dark' ? 'rgba(255,255,255,0.4)' : grey[900]
|
||||||
|
|
||||||
const startTimer = () => {
|
const startTimer = () => {
|
||||||
clearInterval(intervalRef.current)
|
clearInterval(intervalRef.current)
|
||||||
@@ -125,8 +139,38 @@ export default function Lekte({audio, url, parole}) {
|
|||||||
setIsPlaying(false)
|
setIsPlaying(false)
|
||||||
setVolume(100)
|
setVolume(100)
|
||||||
setPosition(0)
|
setPosition(0)
|
||||||
|
setDuration(0)
|
||||||
}, [audio])
|
}, [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 => {
|
const handleChangePosition = value => {
|
||||||
setPosition(value)
|
setPosition(value)
|
||||||
audioRef.current.currentTime = value
|
audioRef.current.currentTime = value
|
||||||
@@ -273,6 +317,31 @@ export default function Lekte({audio, url, parole}) {
|
|||||||
/>
|
/>
|
||||||
<VolumeUpRounded htmlColor={lightIconColor} />
|
<VolumeUpRounded htmlColor={lightIconColor} />
|
||||||
</Stack>
|
</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>
|
</Widget>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import VweKouteAchte from './vwe-koute-achte'
|
|||||||
|
|
||||||
import DrawerBar from './drawer-bar'
|
import DrawerBar from './drawer-bar'
|
||||||
import Pataje from './pataje'
|
import Pataje from './pataje'
|
||||||
|
import KaraokeModal from './karaoke-modal'
|
||||||
|
|
||||||
const drawerWidth = 240
|
const drawerWidth = 240
|
||||||
|
|
||||||
@@ -118,9 +119,6 @@ export default function TeksDrawer({paroles}) {
|
|||||||
<Drawer
|
<Drawer
|
||||||
variant='temporary'
|
variant='temporary'
|
||||||
open={mobileOpen}
|
open={mobileOpen}
|
||||||
ModalProps={{
|
|
||||||
keepMounted: true
|
|
||||||
}}
|
|
||||||
sx={{
|
sx={{
|
||||||
display: {xs: 'block', sm: 'none'},
|
display: {xs: 'block', sm: 'none'},
|
||||||
'& .MuiDrawer-paper': {boxSizing: 'border-box', width: drawerWidth}
|
'& .MuiDrawer-paper': {boxSizing: 'border-box', width: drawerWidth}
|
||||||
@@ -140,6 +138,9 @@ export default function TeksDrawer({paroles}) {
|
|||||||
<DrawerBar meteEsMobilOuve={setMobileOpen} paroles={paroles} />
|
<DrawerBar meteEsMobilOuve={setMobileOpen} paroles={paroles} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</Box>
|
</Box>
|
||||||
|
{parole?.karaokeUrl && (
|
||||||
|
<KaraokeModal url={parole.karaokeUrl} desktopUrl={parole.karaokeDesktopUrl} titre={parole.titre} artistes={parole.artistes} />
|
||||||
|
)}
|
||||||
{success && (
|
{success && (
|
||||||
<Snackbar open={open} autoHideDuration={3000} onClose={handleClose}>
|
<Snackbar open={open} autoHideDuration={3000} onClose={handleClose}>
|
||||||
<Alert severity='success' onClose={handleClose}>
|
<Alert severity='success' onClose={handleClose}>
|
||||||
|
|||||||
+16
-14
@@ -9,7 +9,6 @@ import Card from '@mui/material/Card'
|
|||||||
|
|
||||||
import CardActionArea from '@mui/material/CardActionArea'
|
import CardActionArea from '@mui/material/CardActionArea'
|
||||||
import CardContent from '@mui/material/CardContent'
|
import CardContent from '@mui/material/CardContent'
|
||||||
import CardMedia from '@mui/material/CardMedia'
|
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import Grid from '@mui/material/Grid'
|
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 = {
|
const classes = {
|
||||||
root: `${PREFIX}-root`,
|
root: `${PREFIX}-root`,
|
||||||
media: `${PREFIX}-media`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledGrid = styled(Grid)({
|
const StyledGrid = styled(Grid)({
|
||||||
[`& .${classes.root}`]: {
|
[`& .${classes.root}`]: {
|
||||||
maxWidth: 345
|
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}) {
|
export default function TeksKat({parole}) {
|
||||||
const router = useRouter()
|
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 aliases = getAlias(artistes, parole.prioriteArtistes)
|
||||||
|
|
||||||
const handleClick = slug => {
|
const handleClick = slug => {
|
||||||
@@ -53,13 +47,21 @@ export default function TeksKat({parole}) {
|
|||||||
<StyledGrid size={{xs: 12, sm: 6, md: 4}}>
|
<StyledGrid size={{xs: 12, sm: 6, md: 4}}>
|
||||||
<Card className={classes.root}>
|
<Card className={classes.root}>
|
||||||
<CardActionArea onClick={() => handleClick(slug)}>
|
<CardActionArea onClick={() => handleClick(slug)}>
|
||||||
<CardMedia
|
<Box sx={{position: 'relative', height: 240}}>
|
||||||
className={classes.media}
|
{couverture?.url ? (
|
||||||
component='img'
|
<Image
|
||||||
|
src={`${IMAGE_URL}${couverture.formats?.thumbnail?.url || couverture.url}`}
|
||||||
alt={titre}
|
alt={titre}
|
||||||
image={couverture?.url ? `${IMAGE_URL}${couverture.url}` : noImageUrl}
|
fill
|
||||||
title={titre}
|
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>
|
<CardContent>
|
||||||
<Box sx={{display: 'flex', alignItems: 'center'}}>
|
<Box sx={{display: 'flex', alignItems: 'center'}}>
|
||||||
<Typography display='inline' style={{marginRight: 5}} variant='h6' component='h2'>
|
<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 {styled} from '@mui/material/styles'
|
||||||
import ExplicitIcon from '@mui/icons-material/Explicit'
|
import ExplicitIcon from '@mui/icons-material/Explicit'
|
||||||
|
|
||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
import {formatJsonString, getAlias} from '../../lib/utils/format'
|
import {formatJsonString, getAlias} from '../../lib/utils/format'
|
||||||
|
import {formatKuveti} from '../../lib/kuveti'
|
||||||
|
|
||||||
import LicenseModal from '../cc/license-modal'
|
import LicenseModal from '../cc/license-modal'
|
||||||
import FilesDialog from '../files/files-dialog'
|
import FilesDialog from '../files/files-dialog'
|
||||||
|
import {StreamButton} from '../streaming-buttons'
|
||||||
import EntegreMizik from './entegre-mizik'
|
import EntegreMizik from './entegre-mizik'
|
||||||
import OkiMizik from './oki-mizik'
|
import OkiMizik from './oki-mizik'
|
||||||
import DiferansDialog from './diferans-dialog'
|
import DiferansDialog from './diferans-dialog'
|
||||||
|
|
||||||
|
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
||||||
|
|
||||||
const PREFIX = 'teks'
|
const PREFIX = 'teks'
|
||||||
|
|
||||||
const classes = {
|
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 langToArray = parole => {
|
||||||
const langArray = []
|
const langArray = []
|
||||||
|
|
||||||
if (parole && parole.traductions) {
|
if (parole && parole.traductions) {
|
||||||
const {francais, anglais, espagnol, allemand, italien} = parole.traductions
|
const {francais, anglais, espagnol, allemand, italien} = parole.traductions
|
||||||
|
|
||||||
if (francais) {
|
if (anglais) {
|
||||||
langArray.push({title: 'Traduction', flag: 'fr', lang: francais})
|
langArray.push({title: 'English', lang: anglais})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (anglais) {
|
if (francais) {
|
||||||
langArray.push({title: 'Translation', flag: 'en', lang: anglais})
|
langArray.push({title: 'Français', lang: francais})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (espagnol) {
|
if (espagnol) {
|
||||||
langArray.push({title: 'Traducción', flag: 'es', lang: espagnol})
|
langArray.push({title: 'Español', lang: espagnol})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allemand) {
|
if (allemand) {
|
||||||
langArray.push({title: 'Übersetzung', flag: 'de', lang: allemand})
|
langArray.push({title: 'Deutsch', lang: allemand})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (italien) {
|
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 isMobile = useMediaQuery('(max-width:600px)')
|
||||||
const langArray = langToArray(parole)
|
const langArray = langToArray(parole)
|
||||||
const enhancedAliases = getAlias(parole.artistes, parole.prioriteArtistes, true)
|
const enhancedAliases = getAlias(parole.artistes, parole.prioriteArtistes, true)
|
||||||
|
const coverFmt = formatKuveti(parole.couverture)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isBrowser = () => typeof window !== 'undefined'
|
const isBrowser = () => typeof window !== 'undefined'
|
||||||
@@ -169,6 +178,17 @@ export default function Teks({parole}) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Typography>
|
</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 && (
|
{parole?.user && (
|
||||||
<Typography style={{marginBottom: '1.5em'}} display='block' variant='caption'>
|
<Typography style={{marginBottom: '1.5em'}} display='block' variant='caption'>
|
||||||
<i>parole soumise par {parole.user.username}</i>
|
<i>parole soumise par {parole.user.username}</i>
|
||||||
@@ -181,7 +201,7 @@ export default function Teks({parole}) {
|
|||||||
)}
|
)}
|
||||||
{parole.creativeCommons && (
|
{parole.creativeCommons && (
|
||||||
<Box sx={{maxWidth: 220, margin: '0 auto', textAlign: 'center'}}>
|
<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>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{parole?.files && (
|
{parole?.files && (
|
||||||
@@ -198,47 +218,40 @@ export default function Teks({parole}) {
|
|||||||
{parole?.difference?.length > 0 && (
|
{parole?.difference?.length > 0 && (
|
||||||
<DiferansDialog difference={parole.difference} />
|
<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>
|
</Box>
|
||||||
<Grid container justifycontent='center' spacing={1}>
|
<Grid container justifycontent='center' spacing={1}>
|
||||||
<Grid size={{xs: 12, md: langArray.length > 0 ? 6 : null}}>
|
<Grid size={{xs: 12, md: langArray.length > 0 ? 6 : null}}>
|
||||||
<Box className={classes.gridText}>
|
<Box className={classes.gridText}>
|
||||||
<Typography align='center' sx={{marginBottom: '0.5em'}} variant='h4'>
|
<Typography align='center' sx={{marginBottom: '0.5em'}} variant='h4'>
|
||||||
Transcription
|
Transcription
|
||||||
|
{parole.langueSource && parole.langueSource !== 'ka' && (
|
||||||
|
<Typography component='span' variant='body2' color='text.secondary' sx={{ml: 1}}>
|
||||||
|
({LANG_NAMES[parole.langueSource]})
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography paragraph='true' align={alignTeks(langArray, isMobile)} component='span'>
|
<Typography paragraph='true' align={alignTeks(langArray, isMobile)} component='span'>
|
||||||
{formatJsonString(parole.transcription)}
|
{formatJsonString(parole.transcription)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
{langArray.map(({title, flag, lang}) => (
|
{langArray.map(({title, lang}) => (
|
||||||
<Grid key={title} size={{xs: 12, md: 6}}>
|
<Grid key={title} size={{xs: 12, md: 6}}>
|
||||||
<Box className={classes.gridText}>
|
<Box className={classes.gridText}>
|
||||||
<Typography align='center' sx={{marginBottom: '0.5em'}} variant='h4'>
|
<Typography align='center' sx={{marginBottom: '0.5em'}} variant='h4'>
|
||||||
{flag === 'fr' && (
|
{title}
|
||||||
<span>
|
|
||||||
🇫🇷
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{flag === 'en' && (
|
|
||||||
<span>
|
|
||||||
🇬🇧
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{flag === 'es' && (
|
|
||||||
<span>
|
|
||||||
🇪🇸
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{flag === 'de' && (
|
|
||||||
<span>
|
|
||||||
🇩🇪
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{flag === 'it' && (
|
|
||||||
<span>
|
|
||||||
🇮🇹
|
|
||||||
</span>
|
|
||||||
)} {title}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography paragraph='true' align='justify' component='span'>
|
<Typography paragraph='true' align='justify' component='span'>
|
||||||
{formatJsonString(lang)}
|
{formatJsonString(lang)}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
const {template} = require('lodash')
|
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(`
|
const bodyTemplate = template(`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
@@ -65,18 +68,18 @@ const bodyTemplate = template(`
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<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>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h2 style="margin:0; mso-line-height-rule:exactly;">Merci !</h2><br>
|
<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>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<section>
|
<section>
|
||||||
<h4>Une aide indispensable</h4>
|
<h4>Une aide indispensable</h4>
|
||||||
<p>
|
<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>
|
||||||
<p>
|
<p>
|
||||||
Toute contribution, aussi modeste soit-elle, nous permet d’augmenter nos capacités.<br />
|
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>Pour toute question, n’hésitez pas à nous contacter en répondant à ce courriel.</p>
|
||||||
<p>Merci infiniment pour votre soutien.</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>
|
</footer>
|
||||||
</html>
|
</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 => {
|
const SIZE_ORDER = {
|
||||||
if (!kuveti) {
|
large: ['large', 'medium', 'small'],
|
||||||
return null
|
medium: ['medium', 'small', 'large'],
|
||||||
}
|
small: ['small', 'medium', 'large'],
|
||||||
|
}
|
||||||
|
|
||||||
if (kuveti && kuveti.formats && kuveti.formats.large) {
|
export const formatKuveti = (kuveti, preferred = 'large') => {
|
||||||
return kuveti.formats.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
|
return kuveti
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-2
@@ -56,6 +56,25 @@ export async function jwennTeksEpiSlug(slug) {
|
|||||||
},
|
},
|
||||||
traductions: {
|
traductions: {
|
||||||
populate: '*'
|
populate: '*'
|
||||||
|
},
|
||||||
|
pawol: {
|
||||||
|
populate: '*'
|
||||||
|
},
|
||||||
|
sourceOriginale: {
|
||||||
|
fields: ['titre', 'slug', 'annee'],
|
||||||
|
populate: {
|
||||||
|
artistes: {
|
||||||
|
fields: ['alias', 'slug']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remixes: {
|
||||||
|
fields: ['titre', 'slug', 'annee'],
|
||||||
|
populate: {
|
||||||
|
artistes: {
|
||||||
|
fields: ['alias', 'slug']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
@@ -73,7 +92,7 @@ export async function jwennTeksEpiSlug(slug) {
|
|||||||
|
|
||||||
export async function jwennAwtisEpiSlug(slug) {
|
export async function jwennAwtisEpiSlug(slug) {
|
||||||
const query = qs.stringify({
|
const query = qs.stringify({
|
||||||
populate: ['paroles', 'photo', 'paroles.couverture'],
|
populate: ['paroles', 'photo', 'paroles.couverture', 'titrePhare', 'titrePhare.streamAudio', 'titrePhare.streamVideo', 'titrePhare.couverture'],
|
||||||
filters: {
|
filters: {
|
||||||
slug: {
|
slug: {
|
||||||
$eq: slug
|
$eq: slug
|
||||||
@@ -92,7 +111,7 @@ export async function jwennAwtisPajinasyon(paj) {
|
|||||||
const start = AWTIS_POU_CHAK_PAJ * (paj - 1)
|
const start = AWTIS_POU_CHAK_PAJ * (paj - 1)
|
||||||
const query = qs.stringify({
|
const query = qs.stringify({
|
||||||
populate: ['paroles', 'photo'],
|
populate: ['paroles', 'photo'],
|
||||||
sort: ['createdAt:desc'],
|
sort: ['isExclusiveArtist:desc', 'createdAt:desc'],
|
||||||
pagination: {
|
pagination: {
|
||||||
start,
|
start,
|
||||||
limit: AWTIS_POU_CHAK_PAJ
|
limit: AWTIS_POU_CHAK_PAJ
|
||||||
@@ -210,6 +229,25 @@ export async function jwennDenyeTeks() {
|
|||||||
return data
|
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) {
|
export async function jwennAnTeks(teksId, token) {
|
||||||
const headers = {
|
const headers = {
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
|
|||||||
+7
-17
@@ -4,10 +4,10 @@ import TelegramIcon from '@mui/icons-material/Telegram'
|
|||||||
import PeertubeIcon from './icons/peertube'
|
import PeertubeIcon from './icons/peertube'
|
||||||
import XmppIcon from './icons/xmpp'
|
import XmppIcon from './icons/xmpp'
|
||||||
import XIcon from './icons/x'
|
import XIcon from './icons/x'
|
||||||
import CodebergIcon from './icons/codeberg'
|
import GiteaIcon from './icons/gitea'
|
||||||
import BlueskyIcon from './icons/bluesky'
|
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 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 xmppContact = process.env.NEXT_PUBLIC_XMPP || 'oki@xmpp.cz'
|
||||||
const gadeUsername = process.env.NEXT_PUBLIC_GADE_USERNAME || 'oki'
|
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 = [
|
export const rezoLis = [
|
||||||
{
|
{
|
||||||
tit: 'Codeberg',
|
tit: 'Git',
|
||||||
lyen: codebergURL,
|
lyen: gitURL,
|
||||||
icon: <CodebergIcon />
|
icon: <GiteaIcon />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tit: 'Bluesky',
|
tit: 'Bluesky',
|
||||||
lyen: blueskyUrl,
|
lyen: blueskyUrl,
|
||||||
icon: <BlueskyIcon />
|
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',
|
tit: 'Telegram',
|
||||||
lyen: `https://t.me/${telegramGroup}`,
|
lyen: `https://t.me/${telegramGroup}`,
|
||||||
@@ -47,8 +37,8 @@ export const rezoLis = [
|
|||||||
icon: <YouTubeIcon />
|
icon: <YouTubeIcon />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tit: 'Twitter',
|
tit: 'X',
|
||||||
lyen: `https://twitter.com/${tiwtterUsername}`,
|
lyen: `https://x.com/${tiwtterUsername}`,
|
||||||
icon: <XIcon />
|
icon: <XIcon />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+17
-24
@@ -5,6 +5,22 @@ const withPWA = require('next-pwa')({
|
|||||||
skipWaiting: true
|
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({
|
module.exports = (withPWA({
|
||||||
turbopack: {},
|
turbopack: {},
|
||||||
webpack: config => {
|
webpack: config => {
|
||||||
@@ -16,29 +32,6 @@ module.exports = (withPWA({
|
|||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: buildRemotePatterns()
|
||||||
{
|
|
||||||
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/**',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
+6
-4
@@ -5,13 +5,13 @@
|
|||||||
"private": false,
|
"private": false,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Cédric Famibelle-Pronzola",
|
"name": "ORGANSATION KA INTERNATIONALE",
|
||||||
"email": "contact@cedric-pronzola.dev",
|
"email": "kontak@o-k-i.net",
|
||||||
"url": "https://cedric-pronzola.dev"
|
"url": "https://o-k-i.net"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"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": {
|
"scripts": {
|
||||||
"lint": "xo",
|
"lint": "xo",
|
||||||
@@ -40,8 +40,10 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"music-metadata-browser": "^2.5.11",
|
||||||
"next": "16.2.4",
|
"next": "16.2.4",
|
||||||
"next-auth": "^5.0.0-beta.31",
|
"next-auth": "^5.0.0-beta.31",
|
||||||
|
"next-plausible": "^4.0.0",
|
||||||
"next-pwa": "5.6.0",
|
"next-pwa": "5.6.0",
|
||||||
"nextjs-toploader": "^3.9.17",
|
"nextjs-toploader": "^3.9.17",
|
||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^6.7.2",
|
||||||
|
|||||||
@@ -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:
|
dependencies:
|
||||||
tslib "^2.8.0"
|
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":
|
"@trysound/sax@0.2.0":
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
||||||
@@ -3618,6 +3623,13 @@
|
|||||||
stylis "^4.3.0"
|
stylis "^4.3.0"
|
||||||
ts-invariant "^0.10.3"
|
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:
|
accepts@~1.3.5, accepts@~1.3.7:
|
||||||
version "1.3.7"
|
version "1.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
|
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"
|
base64-js "^1.3.1"
|
||||||
ieee754 "^1.1.13"
|
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:
|
builtin-modules@^3.0.0, builtin-modules@^3.1.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
|
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
|
||||||
@@ -4449,6 +4469,11 @@ content-disposition@0.5.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "5.1.2"
|
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:
|
content-type@~1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
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"
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
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:
|
execa@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
|
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"
|
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
|
||||||
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
|
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:
|
filelist@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
|
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"
|
resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.0.tgz#2cc886be57738419e57f9aab58f647e5e2160270"
|
||||||
integrity sha512-Wsk07aAxDsntgYJY4h0knZJuTxM73eQ4reRAO+Z1liOh8eMCJ/MoDS8fCui1vGT9mnjtl1sOu3I2i/W1swPYZg==
|
integrity sha512-Wsk07aAxDsntgYJY4h0knZJuTxM73eQ4reRAO+Z1liOh8eMCJ/MoDS8fCui1vGT9mnjtl1sOu3I2i/W1swPYZg==
|
||||||
|
|
||||||
ieee754@^1.1.13:
|
ieee754@^1.1.13, ieee754@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
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"
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
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:
|
memory-fs@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290"
|
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"
|
duplexer2 "^0.1.2"
|
||||||
object-assign "^4.1.0"
|
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:
|
nanoid@^3.3.6:
|
||||||
version "3.3.7"
|
version "3.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
||||||
@@ -7689,6 +7762,11 @@ next-auth@^5.0.0-beta.31:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@auth/core" "0.41.2"
|
"@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:
|
next-pwa@5.6.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/next-pwa/-/next-pwa-5.6.0.tgz#f7b1960c4fdd7be4253eb9b41b612ac773392bf4"
|
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"
|
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
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:
|
picocolors@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
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"
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
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:
|
prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||||
version "15.7.2"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
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"
|
string_decoder "^1.1.1"
|
||||||
util-deprecate "^1.0.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:
|
readable-stream@~1.0.17, readable-stream@~1.0.27-1:
|
||||||
version "1.0.34"
|
version "1.0.34"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
|
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"
|
isarray "0.0.1"
|
||||||
string_decoder "~0.10.x"
|
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:
|
redent@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9"
|
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"
|
define-properties "^1.2.1"
|
||||||
es-object-atoms "^1.0.0"
|
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"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
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"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
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:
|
styled-jsx@5.1.6:
|
||||||
version "5.1.6"
|
version "5.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.6.tgz#83b90c077e6c6a80f7f5e8781d0f311b2fe41499"
|
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"
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
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:
|
tr46@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
|
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
|
||||||
|
|||||||
Reference in New Issue
Block a user