Aller au contenu principal

Créer notre propre composant React Docusaurus et proposer un bouton « Partager sur Bluesky »

Créez un composant React Docusaurus personnalisé qui ajoute un bouton « Partager sur Bluesky » à vos articles de blog.

· 11 minutes de lecture
Voir la source
Christophe
Amoureux de Markdown, WSL et Docker ~ Curieux insatiable.
Cet article existe aussi en anglais — lire la version originale.

Traduction automatique

Cette traduction a été produite par une IA. Elle peut contenir des contresens ou des maladresses, en particulier sur les termes techniques. En cas de doute, consultez l'article de référence en anglais — c'est lui qui fait foi.

Journal des modifications

Dernière
Add a live screenshot of the Share button and link to the unified engagement facepile (likes + reposts + commenters) added to the companion article.

Créer notre propre composant React Docusaurus et proposer un bouton « Partager sur Bluesky »

⚡
TL;DR

Cet article vous guide dans la création d'un bouton « Partager sur Bluesky » pour votre blog Docusaurus. Vous apprendrez à utiliser la fonctionnalité swizzle de Docusaurus pour surcharger le thème par défaut des articles, à créer un composant React personnalisé pour le bouton, puis à l'intégrer dans vos articles. Nous construirons le composant étape par étape, avec le CSS et l'icône SVG nécessaires.

Il y a neuf mois, j'avais activé la possibilité d'écrire des commentaires sous mes articles grâce à Giscus (voir Adding a comment area) mais seules les personnes ayant un compte GitHub pouvaient utiliser cette fonctionnalité et donc, presque personne ne l'a fait.

J'ai donc passé près de neuf mois sans donner aux lecteurs de ce blog l'occasion d'interagir avec moi, ce qui est un peu frustrant et nous empêche d'échanger des idées et de nous enrichir mutuellement.

Depuis quelques semaines, je suis présent sur Bluesky et... pourquoi ne pas ajouter un bouton Partager sur Bluesky sous chaque article ?

L'idée n'est ni révolutionnaire ni compliquée alors, jouons...

Dans ce premier article d'une série de deux, nous allons voir comment créer notre composant React Docusaurus. Avant d'écrire cet article, je ne savais pas comment faire et maintenant, si : c'est très simple.

Résultat​

Et, tadaaa 🎉🎉🎉, voici un bouton « Partager sur Bluesky » en action, sous un article de blog :

http://localhost/

Le bouton de partage Bluesky

C'est tout l'objet de cet article : un petit composant React personnalisé, swizzlé dans le BlogPostItem de Docusaurus, avec rien de plus exotique qu'un <button> et une icône SVG. Construisons-le.

Information

Dans un second article (voir Afficher les commentaires Bluesky sur Docusaurus, ainsi que le nombre de likes/reposts), nous sommes allés plus loin : récupérer le nombre de likes, de reposts et de commentaires publiés sur Bluesky et les afficher sous nos articles sous forme d'une seule « engagement facepile » unifiée.

Voici à quoi ressemble ce bouton aujourd'hui, en direct sur ce blog, pour un article récent qui n'a encore récolté aucune interaction Bluesky :

http://localhost/

Le bouton de partage, en direct sur le blog aujourd&#39;hui

Comme je n'avais absolument aucune idée de par où commencer, demandons à une IA de générer un squelette. J'ai demandé quelque chose comme « Bonjour mon ami, j'aimerais afficher un bouton de partage Bluesky sous mes articles sur mon blog Docusaurus. Donne-moi un squelette, s'il te plaît. »

Une fois le code généré par l'IA, il est temps de lire, comprendre et faire quelques tests.

Nous devons surcharger la façon dont l'article est rendu par Docusaurus​

J'ai donc appris que je devais créer un fichier appelé src/theme/BlogPostItem/index.js et simplement y mettre du contenu.

L'IA me donne le contenu mais... hé mon ami, d'où vient ce contenu ? Après quelques recherches, j'apprends qu'il s'agit d'une surcharge du template standard @docusaurus/theme-classic.

J'apprends aussi qu'on peut extraire le fichier original grâce à l'action appelée Swizzle.

Information

Dans la terminologie Docusaurus, Swizzle peut se comprendre comme « surcharger » : nous allons extraire, ici, un template pour ensuite pouvoir le surcharger.

Le fichier src/theme/BlogPostItem/index.js va personnaliser la mise en page et le contenu de chaque article de blog donc, en modifiant ce fichier, je peux changer l'apparence de tous les articles de mon blog.

Puisque je veux ajouter un bouton de partage, c'est le candidat parfait. L'IA a raison.

Extraire le fichier original​

Ouvrez une console et assurez-vous d'être dans le dossier racine de votre blog.

Dans votre console, lancez yarn docusaurus swizzle @docusaurus/theme-classic BlogPostItem.

Lorsque la question est posée, sélectionnez Javascript, puis Eject et enfin YES.

Swizzle du layout BlogPostItem

Comme vous pouvez le voir, beaucoup de fichiers vont être créés dans la structure de répertoires de notre Docusaurus.

Garder les choses sous contrôle​

Comme vous le voyez à l'écran, vous allez extraire beaucoup de fichiers. Gardons les choses sous contrôle : nous voulons juste ajouter un bouton de partage sous notre article, rien de plus.

Regardez votre site Docusaurus maintenant ; allez dans le dossier src/theme/BlogPostItem et constatez que, oui, vous avez maintenant beaucoup de fichiers et de sous-dossiers. Supprimez simplement tous les sous-dossiers ; nous ne voulons pas les modifier, c'est-à-dire que nous voulons conserver les originaux. Ainsi, la prochaine fois que vous installerez une version plus récente de Docusaurus, vous resterez à jour.

Conservez uniquement le fichier index.js créé par la commande swizzle. Voici celui que j'ai obtenu avec ma version actuelle de Docusaurus :

import React from "react";
import clsx from "clsx";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import BlogPostItemContainer from "@theme/BlogPostItem/Container";
import BlogPostItemHeader from "@theme/BlogPostItem/Header";
import BlogPostItemContent from "@theme/BlogPostItem/Content";
import BlogPostItemFooter from "@theme/BlogPostItem/Footer";
// apply a bottom margin in list view
function useContainerClassName() {
const { isBlogPostPage } = useBlogPost();
return !isBlogPostPage ? "margin-bottom--xl" : undefined;
}
export default function BlogPostItem({ children, className }) {
const containerClassName = useContainerClassName();
return (
<BlogPostItemContainer className={clsx(containerClassName, className)}>
<BlogPostItemHeader />
<BlogPostItemContent>{children}</BlogPostItemContent>
<BlogPostItemFooter />
</BlogPostItemContainer>
);
}

Ce composant React affiche un article de blog en combinant un en-tête, le contenu et un pied de page dans un conteneur. Il ajoute automatiquement une marge inférieure lorsque l'article est affiché dans une liste plutôt que sur sa propre page, en utilisant un hook personnalisé pour déterminer le contexte d'affichage.

Jouons et ajoutons un message stupide à chaque article​

Avant d'aller plus loin, faisons un test : est-ce bien le fichier à modifier pour notre besoin ?

Jouons et ajoutons une ligne juste après le titre de notre article. Regardez l'exemple ci-dessous ; je viens d'ajouter une nouvelle ligne :

import React from "react";
import clsx from "clsx";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import BlogPostItemContainer from "@theme/BlogPostItem/Container";
import BlogPostItemHeader from "@theme/BlogPostItem/Header";
import BlogPostItemContent from "@theme/BlogPostItem/Content";
import BlogPostItemFooter from "@theme/BlogPostItem/Footer";
// apply a bottom margin in list view
function useContainerClassName() {
const { isBlogPostPage } = useBlogPost();
return !isBlogPostPage ? "margin-bottom--xl" : undefined;
}
export default function BlogPostItem({ children, className }) {
const containerClassName = useContainerClassName();
return (
<BlogPostItemContainer className={clsx(containerClassName, className)}>
<BlogPostItemHeader />
// highlight-next-line
{/* Just after the blog post title, we'll add a "Are you ready" text */}
// highlight-next-line
<strong style={{ color: "red" }}>
Are you ready to update your BlogPostItem layout?
</strong>
<BlogPostItemContent>{children}</BlogPostItemContent>
<BlogPostItemFooter />
</BlogPostItemContainer>
);
}

Ce composant affiche un article de blog en assemblant plusieurs sous-composants (en-tête, contenu, pied de page) à l'intérieur d'un conteneur. Il ajoute automatiquement une marge inférieure quand on affiche une liste d'articles, mais pas sur la page de l'article complet. Le composant inclut également un texte rouge d'avertissement pour indiquer qu'on peut personnaliser ce layout.

Sauvegardez le fichier, rafraîchissez la page dans votre navigateur et tadaaa 🎉🎉🎉... Oui, on voit ce texte sur chaque article.

Sur l'image ci-dessus, on peut voir notre message stupide :

http://localhost/

Are you ready?

Remarque

Si ça n'a pas fonctionné, arrêtez et relancez votre serveur Docusaurus. Dans mon cas (j'utilise Docker), il me suffit d'arrêter et de relancer mon container.

Donc, comme vous le voyez, c'est maintenant assez simple. Nous pouvons changer la mise en page de notre page Docusaurus juste en manipulant le fichier index.js.

Il est temps de créer notre composant Docusaurus Bluesky​

Nous allons procéder étape par étape en gardant quelques points en tête :

Points à retenir

  • Nous devons minimiser nos changements dans le fichier original src/theme/BlogPostItem/index.js ; le moins de modifications, le mieux ;
  • Nous allons créer un composant Docusaurus dans src/components/ pour permettre les interactions avec Bluesky (pensez à l'évolution du composant) donc, pour cette raison, nous mettrons tout ensemble (code, image, css) et
  • Nous essaierons de garder les choses propres et maintenables pour le développeur, et configurables pour l'utilisateur.

1/5 La structure de notre composant Bluesky générique​

Le premier fichier que nous allons créer sera notre fichier index.tsx, c'est-à-dire notre composant Bluesky global.

Dans cet article, nous allons juste prévoir un bouton « Partager sur Bluesky » mais dans le second article, nous ajouterons d'autres fonctionnalités.

Ce index.tsx sera le point d'entrée du composant.

Créez le fichier src/components/Bluesky/index.tsx avec ce contenu :

import BlueskyShare from "./share";
import styles from "./styles.module.css";
interface Props {
metadata: {
frontMatter?: {
blueskyRecordKey?: string;
};
};
}
export default function Bluesky({ metadata }: Props) {
return (
<div className={styles.blueskyContainer}>
<BlueskyShare metadata={metadata} />
</div>
);
}

Ce composant React affiche un conteneur stylisé qui enveloppe le composant BlueskyShare, en lui transmettant les métadonnées reçues. Il sert principalement de wrapper pour organiser l'interface utilisateur liée au partage Bluesky.

Voilà notre composant React <Bluesky /> ! Simple, non ? Pour l'instant, c'est juste un div contenant un autre composant appelé <BlueskyShare />.

Si vous regardez le code ci-dessus, nous devons créer deux nouveaux fichiers : share.tsx et styles.module.css.

2/5 Notre bouton « Partager sur Bluesky »​

Ok, maintenant, nous devons créer un deuxième fichier appelé src/components/Bluesky/share.tsx et y copier/coller ce contenu :

import Icon from "./bluesky.svg";
import styles from "./styles.module.css";
import Translate, { translate } from "@docusaurus/Translate";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
// Declared locally rather than imported from useBlueskyEngagement — this file (built in the
// series' first article) stands on its own before that hook exists in the tutorial's timeline.
interface Props {
metadata: {
title?: string;
permalink?: string;
frontMatter?: {
blueskyRecordKey?: string;
};
};
}
export default function BlueskyShare({ metadata }: Props) {
const { siteConfig } = useDocusaurusContext();
const blueskyRecordKey = metadata?.frontMatter?.blueskyRecordKey;
if (blueskyRecordKey) return null;
if (!metadata.title || !metadata.permalink) {
console.debug("<BlueskyShare> Missing required properties", { metadata });
return null;
}
const shareLink =
`https://bsky.app/intent/compose?text=` +
`${encodeURIComponent(metadata.title)}%20${siteConfig.url}${encodeURIComponent(
metadata.permalink,
)}`;
return (
<a
href={shareLink}
target="_blank"
rel="noopener noreferrer"
className={styles.blueskyButton}
aria-label={translate({
id: "bluesky.share.ariaLabel",
message: "Share this post on Bluesky",
})}
>
{/* alt dropped: SVGProps has no such prop — was a no-op DOM attribute even before this
migration, and the link's own aria-label already names the icon. */}
<Icon className={styles.blueskyLogo} />
<Translate id="bluesky.share.label">Share on Bluesky</Translate>
</a>
);
}

This React component renders a share button that lets users post the current article to Bluesky social media. It checks that the article has required metadata (title and URL) before generating a share link, and returns nothing if those details are missing or if the article already has a Bluesky record.

Comme vous le voyez, nous avons défini une fonction appelée BlueskyShare et cette fonction attend un paramètre appelé metadata.

Information

Revenez au fichier src/components/Bluesky/index.tsx que nous venons de créer. Nous avions cette ligne : <BlueskyShare metadata={metadata} /> donc, oui, nous passons bien ce paramètre.

Le paramètre metadata est un objet initialisé par Docusaurus et qui contiendra toutes les données du front matter du fichier Markdown de l'article courant. Il inclut des propriétés comme title, description, date, tags, et tous les autres champs personnalisés que vous avez définis dans le front matter.

Que fait la fonction BlueskyShare ? En fait, nous allons juste proposer un bouton avec une url du type https://bsky.app/intent/compose?text=<title>%20<url> (où nous injecterons le titre et l'url de l'article).

Si vous regardez le code ci-dessus, nous devons créer un autre fichier : bluesky.svg.

Évidemment, il nous faut le logo Bluesky quelque part.

Créez un troisième fichier appelé src/components/Bluesky/bluesky.svg et copiez/collez ce contenu dedans :

<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 600 530" xmlns="http://www.w3.org/2000/svg">
<path d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z" fill="#1185fe"/>
</svg>

This is an SVG file that draws a single blue star shape. The star is defined using a path element with a complex set of coordinates and curves, and it's filled with a bright blue color (#1185fe).

4/5 Et enfin notre CSS​

Et, très important, il nous faut du CSS. Créez un quatrième fichier appelé src/components/Bluesky/styles.module.css et copiez/collez ce contenu dedans :

.blueskyLogo {
width: 20px;
height: 20px;
}
.blueskyContainer {
margin: 2rem 0 0.5rem;
padding: 1.1rem 1.25rem 1.4rem;
border: 1px solid var(--ifm-color-emphasis-200);
border-radius: 8px;
background: var(--ifm-background-surface-color);
}
.blueskyHeadline {
display: block;
font-weight: 500;
color: var(--ifm-color-content);
margin-bottom: 0.75rem;
}
.blueskyAccountNote {
display: block;
margin-top: 0.6rem;
font-size: smaller;
color: var(--ifm-color-emphasis-600);
}
.blueskyCommentsContainer {
border: 1px solid var(--ifm-color-emphasis-200);
margin-top: 1rem;
padding: 0.4rem 0.9rem;
margin-bottom: 1.25rem;
}
.blueskyCommentContainer {
margin-bottom: 1rem;
background-color: var(--ifm-color-emphasis-100);
padding: 0.8rem;
border-radius: 6px;
border-left: 2px solid var(--ifm-color-emphasis-300);
color: var(--ifm-font-color-base);
}
.blueskyCommentHeader {
display: flex;
align-items: center;
gap: 10px;
}
.blueskyCommentAuthorInfos {
display: flex;
flex-direction: column;
}
.blueskyCommentAvatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 0.5rem;
}
.blueskyCommentAuthorDisplayName {
margin: 0;
font-weight: bold;
}
.blueskyCommentAuthorHandle {
margin: 0;
font-size: smaller;
font-weight: normal;
color: var(--ifm-color-emphasis-600);
display: block;
}
.blueskyCommentDate {
margin-left: auto;
font-size: smaller;
color: var(--ifm-color-emphasis-600);
}
.blueskyCommentText {
margin: 0.3rem 0 0;
}
.blueskyCommentLikes::before {
content: "❤️\a0";
}
.blueskyCommentLikes::after {
content: " ";
}
.blueskyCommentReposts::before {
content: "🔁\a0";
}
.blueskyCommentReposts::after {
content: " ";
}
.blueskyCommentLink::before {
content: "→\a0";
}
.blueskyCommentLink::after {
content: " ";
}
.blueskyCommentLink {
margin-right: 0.25rem;
display: inline-flex;
align-items: center;
color: var(--ifm-color-emphasis-600);
text-decoration: none;
margin-top: 0.25rem;
}
img.blueskyCommentEmbedThumb {
max-height: 250px;
}
.blueskyCommentImages {
margin-top: 0.5rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.blueskyCommentImage {
max-width: 100%;
height: auto;
border-radius: 0.25rem;
border: 1px solid var(--ifm-color-emphasis-300);
}
.blueskyNoCommentYet {
color: var(--ifm-color-emphasis-600);
font-size: 1rem;
line-height: 1.5;
text-align: center;
padding: 20px;
background-color: var(--ifm-color-emphasis-100);
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 8px;
margin: 20px 0;
}
.blueskyNoCommentYetCTA {
font-weight: bold;
color: var(--ifm-color-primary-dark);
cursor: pointer;
text-decoration: underline;
}
[data-theme="dark"] .blueskyNoCommentYetCTA {
color: var(--ifm-color-primary-light);
}
.blueskyPostLikes {
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
.blueskyLikersAvatars {
display: inline-flex;
align-items: center;
margin-left: 0.4rem;
}
.blueskyLikersAvatarLink {
margin-left: -1rem;
line-height: 0;
}
.blueskyLikersAvatarLink:first-child {
margin-left: 0;
}
.blueskyLikersAvatar {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid var(--ifm-background-color);
display: block;
}
.blueskyLikersAvatarMore {
margin-left: 0.4rem;
font-size: smaller;
color: var(--ifm-color-emphasis-600);
}
.blueskyEngagementRow {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.blueskyButton {
align-items: center;
background-color: var(--ifm-color-emphasis-200);
border-radius: 6px;
border: 1px solid var(--ifm-color-emphasis-300);
box-shadow: none;
color: var(--ifm-font-color-base);
display: inline-flex;
font-size: 0.9rem;
font-weight: 400;
gap: 0.4rem;
margin-bottom: 0.8rem;
margin-right: 0.4rem;
padding: 0.4rem 0.9rem;
text-decoration: none;
transition:
background-color 0.2s ease,
color 0.2s ease;
}
.blueskyButton:hover {
background-color: var(--ifm-color-primary);
border-color: var(--ifm-color-primary);
color: white;
}

This stylesheet styles a Bluesky social media embed component with various sections for displaying posts, comments, user avatars, and engagement metrics. It uses CSS variables for theming to support both light and dark modes, and includes hover effects for interactive elements like buttons and links.

5/5 Dernière chose, nous devons implémenter notre composant dans Docusaurus​

Notre composant Bluesky est prêt. Nous avons créé nos quatre fichiers.

Vous devriez avoir ceci :

src

▼components/Bluesky
bluesky.svg
index.tsx
share.tsx
styles.module.css
▼theme/BlogPostItem
index.js

Revenons à ce qui a été dit plus tôt : pour adapter la mise en page, Docusaurus utilise le fichier src/theme/BlogPostItem/index.js. Il est donc temps d'injecter notre composant fraîchement créé dans ce script.

import React from "react";
import clsx from "clsx";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import BlogPostItemContainer from "@theme/BlogPostItem/Container";
import BlogPostItemHeader from "@theme/BlogPostItem/Header";
import BlogPostItemContent from "@theme/BlogPostItem/Content";
import BlogPostItemFooter from "@theme/BlogPostItem/Footer";
// highlight-next-line
// import our Bluesky component
// highlight-next-line
import Bluesky from "@site/src/components/Bluesky";
// apply a bottom margin in list view
function useContainerClassName() {
const { isBlogPostPage } = useBlogPost();
return !isBlogPostPage ? "margin-bottom--xl" : undefined;
}
export default function BlogPostItem({ children, className }) {
// highlight-next-line
// We need to retrieve the isBlogPostPage flag
// highlight-next-line
const { metadata, isBlogPostPage } = useBlogPost();
const containerClassName = useContainerClassName();
return (
<BlogPostItemContainer className={clsx(containerClassName, className)}>
<BlogPostItemHeader />
<BlogPostItemContent>{children}</BlogPostItemContent>
<BlogPostItemFooter />
// highlight-next-line
{/* Only display Bluesky components on the post page; not the blog view */}
// highlight-next-line
{isBlogPostPage && <Bluesky metadata={metadata} />}
</BlogPostItemContainer>
);
}

Ce composant React affiche un article de blog avec ses sections standard (en-tête, contenu, pied de page) et ajoute un composant Bluesky personnalisé qui s'affiche uniquement quand on consulte l'article complet, pas dans la liste des articles. Il gère aussi l'espacement visuel différent selon qu'on est sur la page d'un article ou sur la liste.

Félicitations ! Si vous allez sur une page de votre blog et que vous la rafraîchissez, vous verrez le bouton Bluesky en bas de page.

Remarque

Si ça n'a pas fonctionné, arrêtez et relancez votre serveur Docusaurus. Dans mon cas (j'utilise Docker), il me suffit d'arrêter et de relancer mon container.

Rafraîchissez la page d'un article et vous verrez le même bouton que celui présenté en début d'article, maintenant en place sur votre propre site.

Tous les fichiers d'un coup d'œil​

Voici l'ensemble complet des fichiers créés dans cet article. Utilisez le bouton ci-dessous pour les copier tous d'un coup, ou téléchargez-les sous forme de ZIP prêt à extraire :

📦 Mise en place du projet : Bluesky share button
Aide

Besoin d'aide ? Cliquez sur le bouton « Aide » ci-dessus pour les instructions de mise en place.

Dans un prochain article, nous apprendrons à interagir avec Bluesky :

  • Proposer un bouton sous l'article pour sauter vers les posts Bluesky afin que vos visiteurs puissent :
    • Liker votre article (ce qui est toujours agréable, non ?),
    • Partager votre article (encore mieux) et
    • Lancer ou participer à une discussion
  • Nous afficherons aussi le nombre de likes et de reposts sur le post Bluesky et
  • Récupérer tous les commentaires publiés sur Bluesky et les afficher sur votre blog ; cela augmentera sans aucun doute l'engagement de vos visiteurs.

Remarques finales - Attention à la commande swizzle​

Vous devez comprendre qu'en swizzlant (surchargeant) le layout BlogPostItem, vous n'êtes plus aligné avec le layout standard de Docusaurus.

Si, dans une prochaine release, de nouvelles fonctionnalités sont ajoutées par Docusaurus, vous ne les aurez pas ! puisque vous n'utilisez plus le layout par défaut pour vos articles.

Gardez-le simplement en tête et, peut-être, de temps en temps (après une release majeure par exemple), pensez à relancer la commande swizzle et à refaire votre personnalisation.

C'est pour cette raison que nos modifications du fichier index.js ont été réduites au minimum.


🦋 Rejoignez la conversation sur BlueskyUn compte Bluesky gratuit est nécessaire pour aimer, repartager ou commenter. Ce bloc interroge l'API publique de Bluesky directement depuis votre navigateur (sans cookie, sans connexion) — votre adresse IP parvient donc aux serveurs de Bluesky dès que cette section se charge.

Chargement des commentaires…

Cet article vous a-t-il été utile ?
Avez-vous suivi les étapes de cet article ?