4 Commits
Author SHA1 Message Date
cedric 99daeb014c feat: styliser le lien vers le fil BOKANTE
Déploiement FRONT BETA / check (push) Successful in 2m7s
Déploiement FRONT PROD / check (push) Successful in 2m15s
Déploiement FRONT BETA / deploy (push) Successful in 24s
Déploiement FRONT PROD / deploy (push) Successful in 24s
2026-07-07 16:13:57 +04:00
cedric f5a4014e41 feat: ajouter un sélecteur d'instance Mastodon et expliciter BOKANTE 2026-07-07 16:11:46 +04:00
cedric d66c679fd1 revert: ne plus présélectionner selon la langue du navigateur
Déploiement FRONT BETA / check (push) Successful in 2m11s
Déploiement FRONT PROD / check (push) Successful in 2m11s
Déploiement FRONT BETA / deploy (push) Successful in 21s
Déploiement FRONT PROD / deploy (push) Successful in 20s
2026-07-06 02:34:08 +04:00
cedric cab293d62e Merge pull request 'Sélecteur de langue : regroupement par continent, Afrique en tête' (#6) from feat/africa into master
Déploiement FRONT BETA / check (push) Successful in 2m11s
Déploiement FRONT PROD / check (push) Successful in 2m16s
Déploiement FRONT BETA / deploy (push) Successful in 22s
Déploiement FRONT PROD / deploy (push) Successful in 19s
Reviewed-on: #6
2026-07-05 22:20:46 +00:00
3 changed files with 115 additions and 55 deletions
@@ -15,19 +15,36 @@ describe('ReponsMastodon', () => {
expect(container).toBeEmptyDOMElement() expect(container).toBeEmptyDOMElement()
}) })
it('affiche un lien direct vers le fil bokante', () => { it('affiche un lien direct vers le fil BOKANTE', () => {
render(<ReponsMastodon bokanteStatusId='112233' />) render(<ReponsMastodon bokanteStatusId='112233' />)
const lien = screen.getByRole('link', {name: 'Mastodon'}) const lien = screen.getByRole('link', {name: 'BOKANTE (Mastodon)'})
expect(lien).toHaveAttribute('href', 'https://bokante.o-k-i.net/@pawol_nu/112233') expect(lien).toHaveAttribute('href', 'https://bokante.o-k-i.net/@pawol_nu/112233')
}) })
it('redirige vers authorize_interaction avec le domaine extrait d\'un identifiant complet', async () => { it('redirige vers BOKANTE par défaut quand on continue', async () => {
window.open = vi.fn() window.open = vi.fn()
const user = userEvent.setup() const user = userEvent.setup()
render(<ReponsMastodon bokanteStatusId='112233' />) render(<ReponsMastodon bokanteStatusId='112233' />)
await user.click(screen.getByRole('button', {name: /répondre via mastodon/i})) await user.click(screen.getByRole('button', {name: /répondre sur mastodon/i}))
await user.type(screen.getByLabelText(/@vous@votre-instance/i), '@quelqun@mastodon.social') await user.click(screen.getByRole('button', {name: /continuer/i}))
expect(window.open).toHaveBeenCalledWith(
'https://bokante.o-k-i.net/authorize_interaction?uri=' + encodeURIComponent('https://bokante.o-k-i.net/@pawol_nu/112233'),
'_blank',
'noopener,noreferrer'
)
})
it('permet de choisir une autre instance dans le select', async () => {
window.open = vi.fn()
const user = userEvent.setup()
render(<ReponsMastodon bokanteStatusId='112233' />)
await user.click(screen.getByRole('button', {name: /répondre sur mastodon/i}))
await user.click(screen.getByLabelText(/instance mastodon/i))
await user.click(screen.getByRole('option', {name: /autre instance/i}))
await user.type(screen.getByLabelText(/@vous@votre-instance/i), 'mastodon.social')
await user.click(screen.getByRole('button', {name: /continuer/i})) await user.click(screen.getByRole('button', {name: /continuer/i}))
expect(window.open).toHaveBeenCalledWith( expect(window.open).toHaveBeenCalledWith(
@@ -37,17 +54,19 @@ describe('ReponsMastodon', () => {
) )
}) })
it('accepte aussi juste un nom d\'instance sans @', async () => { it('accepte un identifiant complet pour une autre instance', async () => {
window.open = vi.fn() window.open = vi.fn()
const user = userEvent.setup() const user = userEvent.setup()
render(<ReponsMastodon bokanteStatusId='112233' />) render(<ReponsMastodon bokanteStatusId='112233' />)
await user.click(screen.getByRole('button', {name: /répondre via mastodon/i})) await user.click(screen.getByRole('button', {name: /répondre sur mastodon/i}))
await user.type(screen.getByLabelText(/@vous@votre-instance/i), 'mastodon.social') await user.click(screen.getByLabelText(/instance mastodon/i))
await user.click(screen.getByRole('option', {name: /autre instance/i}))
await user.type(screen.getByLabelText(/@vous@votre-instance/i), '@quelqun@mastodon.social')
await user.click(screen.getByRole('button', {name: /continuer/i})) await user.click(screen.getByRole('button', {name: /continuer/i}))
expect(window.open).toHaveBeenCalledWith( expect(window.open).toHaveBeenCalledWith(
expect.stringContaining('https://mastodon.social/authorize_interaction'), 'https://mastodon.social/authorize_interaction?uri=' + encodeURIComponent('https://bokante.o-k-i.net/@pawol_nu/112233'),
'_blank', '_blank',
'noopener,noreferrer' 'noopener,noreferrer'
) )
+77 -14
View File
@@ -10,11 +10,24 @@ import DialogContent from '@mui/material/DialogContent'
import DialogActions from '@mui/material/DialogActions' import DialogActions from '@mui/material/DialogActions'
import TextField from '@mui/material/TextField' import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import Link from 'next/link' import FormControl from '@mui/material/FormControl'
import InputLabel from '@mui/material/InputLabel'
import Select from '@mui/material/Select'
import MenuItem from '@mui/material/MenuItem'
import Link from '@mui/material/Link'
import {Mastodon} from '@icons-pack/react-simple-icons'
const BOKANTE_URL = process.env.NEXT_PUBLIC_BOKANTE_URL || 'https://bokante.o-k-i.net' const BOKANTE_URL = process.env.NEXT_PUBLIC_BOKANTE_URL || 'https://bokante.o-k-i.net'
const BOKANTE_ACCOUNT = process.env.NEXT_PUBLIC_BOKANTE_ACCOUNT || 'pawol_nu' const BOKANTE_ACCOUNT = process.env.NEXT_PUBLIC_BOKANTE_ACCOUNT || 'pawol_nu'
const INSTANCES_SUGGEREES = [
{domain: 'bokante.o-k-i.net', label: 'BOKANTE - notre instance'},
{domain: 'mastodon.social', label: 'mastodon.social'},
{domain: 'piaille.fr', label: 'piaille.fr'},
]
const AUTRE_INSTANCE = 'autre'
function extraireInstance(saisie) { function extraireInstance(saisie) {
const valeur = saisie.trim().replace(/^@/, '').replace(/^https?:\/\//, '').replace(/\/$/, '') const valeur = saisie.trim().replace(/^@/, '').replace(/^https?:\/\//, '').replace(/\/$/, '')
return valeur.includes('@') ? valeur.split('@').pop() : valeur return valeur.includes('@') ? valeur.split('@').pop() : valeur
@@ -22,7 +35,8 @@ function extraireInstance(saisie) {
export default function ReponsMastodon({bokanteStatusId}) { export default function ReponsMastodon({bokanteStatusId}) {
const [ouvert, setOuvert] = useState(false) const [ouvert, setOuvert] = useState(false)
const [saisie, setSaisie] = useState('') const [instance, setInstance] = useState(INSTANCES_SUGGEREES[0].domain)
const [autreInstance, setAutreInstance] = useState('')
if (!bokanteStatusId) { if (!bokanteStatusId) {
return null return null
@@ -31,43 +45,92 @@ export default function ReponsMastodon({bokanteStatusId}) {
const tootUrl = `${BOKANTE_URL}/@${BOKANTE_ACCOUNT}/${bokanteStatusId}` const tootUrl = `${BOKANTE_URL}/@${BOKANTE_ACCOUNT}/${bokanteStatusId}`
const repondre = () => { const repondre = () => {
const instance = extraireInstance(saisie) const domaine = instance === AUTRE_INSTANCE ? extraireInstance(autreInstance) : instance
if (!instance) { if (!domaine) {
return return
} }
window.open(`https://${instance}/authorize_interaction?uri=${encodeURIComponent(tootUrl)}`, '_blank', 'noopener,noreferrer') window.open(`https://${domaine}/authorize_interaction?uri=${encodeURIComponent(tootUrl)}`, '_blank', 'noopener,noreferrer')
setOuvert(false) setOuvert(false)
setAutreInstance('')
} }
return ( return (
<Box sx={{maxWidth: 700, margin: '1em auto 0', textAlign: 'center'}}> <Box sx={{maxWidth: 700, margin: '1em auto 0', textAlign: 'center'}}>
<Typography variant='body2' sx={{mb: 1}}> <Typography variant='body2' sx={{mb: 1}}>
Rejoignez la discussion sur{' '} Rejoignez la discussion sur le Fediverse via{' '}
<Link href={tootUrl} target='_blank' rel='noopener noreferrer'>Mastodon</Link> <Link
href={tootUrl}
target='_blank'
rel='noopener noreferrer'
underline='hover'
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 1,
py: 0.25,
borderRadius: 1,
bgcolor: 'action.hover',
color: 'primary.main',
fontWeight: 500,
textDecoration: 'none',
'&:hover': {
bgcolor: 'action.selected'
}
}}
>
BOKANTE (Mastodon)
</Link>
</Typography> </Typography>
<Button variant='outlined' size='small' onClick={() => setOuvert(true)}> <Button
Répondre via Mastodon variant='outlined'
size='small'
startIcon={<Mastodon size={16} color='#6364FF' title='' />}
onClick={() => setOuvert(true)}
>
Répondre sur Mastodon
</Button> </Button>
<Dialog open={ouvert} onClose={() => setOuvert(false)}> <Dialog open={ouvert} onClose={() => setOuvert(false)}>
<DialogTitle>Répondre depuis votre compte Mastodon</DialogTitle> <DialogTitle>Répondre depuis Mastodon</DialogTitle>
<DialogContent> <DialogContent>
<Typography variant='body2' sx={{mb: 2}}> <Typography variant='body2' sx={{mb: 2}}>
Indiquez votre identifiant (@vous@votre-instance) ou juste le nom de votre Choisissez linstance Mastodon sur laquelle vous avez un compte.
instance, vous serez redirigé·e vers votre compte pour répondre. BOKANTE est linstance de OKI : vous pouvez aussi utiliser
nimporte quelle autre instance du Fediverse.
</Typography> </Typography>
<FormControl fullWidth sx={{mb: 2}}>
<InputLabel id='instance-mastodon-label'>Instance Mastodon</InputLabel>
<Select
labelId='instance-mastodon-label'
id='instance-mastodon'
value={instance}
label='Instance Mastodon'
onChange={event => setInstance(event.target.value)}
>
{INSTANCES_SUGGEREES.map(({domain, label}) => (
<MenuItem key={domain} value={domain}>
{label}
</MenuItem>
))}
<MenuItem value={AUTRE_INSTANCE}>Autre instance</MenuItem>
</Select>
</FormControl>
{instance === AUTRE_INSTANCE && (
<TextField <TextField
autoFocus autoFocus
fullWidth fullWidth
label='@vous@votre-instance' label='@vous@votre-instance'
value={saisie} helperText='Indiquez votre identifiant (@vous@votre-instance) ou juste le nom de votre instance, vous serez redirigé·e vers votre compte pour répondre.'
onChange={event => setSaisie(event.target.value)} value={autreInstance}
onChange={event => setAutreInstance(event.target.value)}
onKeyDown={event => { onKeyDown={event => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
repondre() repondre()
} }
}} }}
/> />
)}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => setOuvert(false)}>Annuler</Button> <Button onClick={() => setOuvert(false)}>Annuler</Button>
-22
View File
@@ -135,14 +135,6 @@ const langToArray = parole => {
.map(({field, title, continent, vedette}) => ({field, title, continent, vedette, lang: parole.traductions[field]})) .map(({field, title, continent, vedette}) => ({field, title, continent, vedette, lang: parole.traductions[field]}))
} }
const BROWSER_LANG_TO_FIELD = {
yo: 'yoruba', ln: 'lingala', wo: 'wolof', sw: 'swahili', ha: 'hausa',
ar: 'arabe', om: 'oromo', ig: 'igbo', zu: 'zoulou', mg: 'malgache',
xh: 'xhosa', tn: 'tswana', ts: 'tsonga', st: 'sesotho',
ja: 'japonais', ko: 'coreen', en: 'anglais', fr: 'francais',
es: 'espagnol', de: 'allemand', it: 'italien', pt: 'portugais',
}
const ExplicitTooltip = Tooltip const ExplicitTooltip = Tooltip
export default function Teks({parole}) { export default function Teks({parole}) {
@@ -167,20 +159,6 @@ export default function Teks({parole}) {
return () => setCurrentTrack(null) return () => setCurrentTrack(null)
}, [parole, setCurrentTrack]) }, [parole, setCurrentTrack])
useEffect(() => {
const browserLang = navigator.language?.split('-')[0]?.toLowerCase()
if (!browserLang || browserLang === parole.langueSource) {
return
}
const champ = BROWSER_LANG_TO_FIELD[browserLang]
const index = langArray.findIndex(lang => lang.field === champ)
if (index !== -1) {
setTab(index + 1)
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- ne doit s'exécuter qu'au montage, pas à chaque changement d'onglet
}, [])
return ( return (
<Root className={classes.container}> <Root className={classes.container}>
<Box sx={{textAlign: 'center', marginTop: 12}}> <Box sx={{textAlign: 'center', marginTop: 12}}>