diff --git a/.env.sample b/.env.sample
index 114d967..65f689a 100644
--- a/.env.sample
+++ b/.env.sample
@@ -81,6 +81,11 @@ NEXT_PUBLIC_GIT=
NEXT_PUBLIC_CODEBERG=
NEXT_PUBLIC_BLUESKY_URL=
+# Commentaires fédérés (bokante.o-k-i.net)
+
+NEXT_PUBLIC_BOKANTE_URL=https://bokante.o-k-i.net
+NEXT_PUBLIC_BOKANTE_ACCOUNT=pawol_nu
+
# DOMAIN IMAGE
NEXT_PUBLIC_DOMAINS_IMAGE="localhost:1337 strapi.mondomaine.com"
diff --git a/components/teks/__tests__/repons-mastodon.test.jsx b/components/teks/__tests__/repons-mastodon.test.jsx
new file mode 100644
index 0000000..afe6b19
--- /dev/null
+++ b/components/teks/__tests__/repons-mastodon.test.jsx
@@ -0,0 +1,55 @@
+import {describe, it, expect, vi, afterEach} from 'vitest'
+import {render, screen} from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import ReponsMastodon from '../repons-mastodon'
+
+describe('ReponsMastodon', () => {
+ const originalOpen = window.open
+
+ afterEach(() => {
+ window.open = originalOpen
+ })
+
+ it('ne rend rien sans bokanteStatusId', () => {
+ const {container} = render()
+ expect(container).toBeEmptyDOMElement()
+ })
+
+ it('affiche un lien direct vers le fil bokante', () => {
+ render()
+ const lien = screen.getByRole('link', {name: 'Mastodon'})
+ 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 () => {
+ window.open = vi.fn()
+ const user = userEvent.setup()
+ render()
+
+ await user.click(screen.getByRole('button', {name: /répondre via 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://mastodon.social/authorize_interaction?uri=' + encodeURIComponent('https://bokante.o-k-i.net/@pawol_nu/112233'),
+ '_blank',
+ 'noopener,noreferrer'
+ )
+ })
+
+ it('accepte aussi juste un nom d\'instance sans @', async () => {
+ window.open = vi.fn()
+ const user = userEvent.setup()
+ render()
+
+ await user.click(screen.getByRole('button', {name: /répondre via mastodon/i}))
+ await user.type(screen.getByLabelText(/@vous@votre-instance/i), 'mastodon.social')
+ await user.click(screen.getByRole('button', {name: /continuer/i}))
+
+ expect(window.open).toHaveBeenCalledWith(
+ expect.stringContaining('https://mastodon.social/authorize_interaction'),
+ '_blank',
+ 'noopener,noreferrer'
+ )
+ })
+})
diff --git a/components/teks/repons-mastodon.jsx b/components/teks/repons-mastodon.jsx
new file mode 100644
index 0000000..fd63b63
--- /dev/null
+++ b/components/teks/repons-mastodon.jsx
@@ -0,0 +1,83 @@
+'use client'
+
+import {useState} from 'react'
+import PropTypes from 'prop-types'
+import Box from '@mui/material/Box'
+import Button from '@mui/material/Button'
+import Dialog from '@mui/material/Dialog'
+import DialogTitle from '@mui/material/DialogTitle'
+import DialogContent from '@mui/material/DialogContent'
+import DialogActions from '@mui/material/DialogActions'
+import TextField from '@mui/material/TextField'
+import Typography from '@mui/material/Typography'
+import Link from 'next/link'
+
+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'
+
+function extraireInstance(saisie) {
+ const valeur = saisie.trim().replace(/^@/, '').replace(/^https?:\/\//, '').replace(/\/$/, '')
+ return valeur.includes('@') ? valeur.split('@').pop() : valeur
+}
+
+export default function ReponsMastodon({bokanteStatusId}) {
+ const [ouvert, setOuvert] = useState(false)
+ const [saisie, setSaisie] = useState('')
+
+ if (!bokanteStatusId) {
+ return null
+ }
+
+ const tootUrl = `${BOKANTE_URL}/@${BOKANTE_ACCOUNT}/${bokanteStatusId}`
+
+ const repondre = () => {
+ const instance = extraireInstance(saisie)
+ if (!instance) {
+ return
+ }
+
+ window.open(`https://${instance}/authorize_interaction?uri=${encodeURIComponent(tootUrl)}`, '_blank', 'noopener,noreferrer')
+ setOuvert(false)
+ }
+
+ return (
+
+
+ Rejoignez la discussion sur{' '}
+ Mastodon
+
+
+
+
+ )
+}
+
+ReponsMastodon.propTypes = {
+ bokanteStatusId: PropTypes.string
+}
diff --git a/components/teks/teks.js b/components/teks/teks.js
index 2c15136..d3a9d00 100644
--- a/components/teks/teks.js
+++ b/components/teks/teks.js
@@ -30,6 +30,7 @@ import EntegreMizik from './entegre-mizik'
import OkiMizik from './oki-mizik'
import DiferansDialog from './diferans-dialog'
import Komante from './komante'
+import ReponsMastodon from './repons-mastodon'
import {useCurrentTrack} from './current-track-context'
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
@@ -282,6 +283,7 @@ export default function Teks({parole}) {
)
))}
+
)