~rom1v/blog { un blog libre }

Commentaires statiques avec Jekyll

Pour ce blog, j’ai abandonné Wordpress pour Jekyll, un moteur de blog statique.

Ainsi, j’écris mes articles en markdown dans mon éditeur favori, je les commite dans un dépôt git, et je génère le blog avec :

jekyll build

Le contenu hébergé étant statique, les pages ainsi générées à partir des sources sont renvoyées telles quelles.

Ce fonctionnement a beaucoup d’avantages :

  • le temps de réponse est minimal ;
  • la sécurité est largement accrue ;
  • la maintenance est simplifiée (pas de mises à jour de sécurité régulières) ;
  • le backup est trivial (git clone, pas de base de données).

Sans commentaires

L’inconvénient, c’est qu’un contenu statique est difficilement conciliable avec le support des commentaires (il faut bien d’une manière ou d’une autre exécuter du code lors de la réception d’un commentaire).

Il y a plusieurs manières de contourner le problème.

Il est par exemple possible d’en déporter la gestion (sur un service en ligne comme Disqus ou un équivalent libre – isso – à héberger soi-même). Ainsi, les commentaires peuvent être chargés séparément par le client en Javascript.

Au lieu de cela, j’ai choisi d’intégrer les commentaires aux sources du blog. Voici comment.

L’objectif est d’une part de pouvoir stocker et afficher les commentaires existants, et d’autre part de fournir aux lecteurs la possibilité d’en soumettre de nouveaux, qui me seront envoyés par e-mail.

Je me suis principalement inspiré du contenu de Jekyll::StaticComments, même si, comme nous allons le voir, je n’utilise pas le plug-in lui-même.

Stockage

L’idée est de stocker les commentaires quelque part dans les sources du site au format YAML.

Le plugin Jekyll::StaticComments nécessite de stocker un fichier par commentaire dans un dossier spécial (_comments) parsé par un script à insérer dans le répertoire _plugins.

Personnellement, je préfère avoir tous les commentaires d’un même post regroupés au sein d’un même fichier. Et pour cela, pas besoin de plug-in : nous pouvons faire correspondre à chaque post dans _posts une liste de commentaires dans _data (un répertoire géré nativement par Jekyll).

Par exemple, ce billet est stocké dans :

_posts/2017-01-09-commentaires-statiques-avec-jekyll.md

Dans l’idéal, je voudrais que les commentaires associés soient stockés dans :

_data/comments-2017-01-09-commentaires-statiques-avec-jekyll.yaml

En pratique, pour des raisons techniques (Jekyll ne donne pas accès au nom du fichier), le nom du fichier ne contient pas le numéro du jour :

_data/comments-2017-01-commentaires-statiques-avec-jekyll.yaml

Il suffit alors de stocker dans ces fichiers les commentaires sous cette forme :

- id: 1
  author: this_is_me
  date: 2017-01-02 10:11:12+01:00
  contents: |
    Bonjour,

    Ceci est un commentaire écrit en _markdown_.
- id: 2
  author: dev42
  author-url: https://github.com
  date: 2017-01-02 12:11:10+01:00
  contents: |
    > Ceci est un commentaire écrit en _markdown_.

    Et ça supporte aussi le [Liquid](https://jekyllrb.com/docs/templates/) :

    {% highlight c %}
    int main() {
        return 0;
    }
    {% endhighlight %}

Pour des exemples réels, voir les sources des commentaires de ce blog.

Affichage

Maintenant que nous avons les données des commentaires, nous devons les afficher.

Il faut d’abord trouver la liste des commentaires associée à la page courante.

Comme nous ne pouvons pas récupérer directement le nom du fichier d’une page, nous devons reconstruire la chaîne à partir de la variable page.id, qui ici vaut :

/2017/01/commentaires-statiques-avec-jekyll

Cette ligne de Liquid :

comments{{ page.id | replace: '/', '-' }}

donne la valeur :

comments-2017-01-commentaires-statiques-avec-jekyll

Nous avons donc tout ce dont nous avons besoin pour créer le template de commentaires (à stocker dans _include/comments.html) :

{% capture commentid %}comments{{ page.id | replace: '/', '-' }}{% endcapture %}
{% if site.data[commentid] %}
<h2 id="comments">Commentaires</h2>
<div class="comments">
    {% for comment in site.data[commentid] %}
        <div id="comment-{{ comment.id }}" class="comment" />
            <div class="comment-author">
                {% if (comment.author-url) %}
                    <a href="{{comment.author-url}}">
                {% endif %}
                {{ comment.author }}
                {% if (comment.author-url) %}
                    </a>
                {% endif %}
            </div>
            <div class="comment-date">
                <a href="#comment-{{ comment.id }}">
                    {{ comment.date | date: "%-d %B %Y, %H:%M" }}
                </a>
            </div>
            <div class="comment-contents">
                {{ comment.contents | liquify | markdownify }}
            </div>
        </div>
    {% endfor %}
</div>

Il suffit alors d’inclure cette page à l’endroit où vous souhaitez insérer les commentaires (typiquement dans _layout/post.html) :

{% include comments.html %}

Formulaire

Pour proposer aux utilisateurs de poster de nouveaux commentaires, il nous faut un formulaire.

À titre d’exemple, voici le mien (intégré à _include/comments.html) :

<h3 class="comment-title">Poster un commentaire</h3>
<form method="POST" action="/comments/submit.php">
    <input type="hidden" name="post_id" value="{{ page.id }}" />
    <input type="hidden" name="return_url" value="{{ page.url }}" />
    <table class="comment-table">
        <tr>
            <th>Nom :</th>
            <td>
                <input type="text" size="25" name="name" />
                <em>(requis)</em>
            </td>
        </tr>
        <tr>
            <th>E-mail :</th>
            <td>
                <input type="text" size="25" name="email" />
                <em>(requis, non publié)</em>
            </td>
        </tr>
        <tr>
            <th>Site web :</th>
            <td>
                <input type="text" size="25" name="url" />
                <em>(optionnel)</em>
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <textarea name="comment" rows="10"></textarea>
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <input class="comment-submit" type="submit" value="Envoyer" />
            </td>
        </tr>
    </table>
</form>

Ce formulaire est affiché sous les commentaires existants.

Traitement

L’action du formulaire précédent pointait sur comments/submit.php. Il nous reste donc à écrire dans ce fichier le code à exécuter lorsqu’un utilisateur envoie un commentaire au serveur.

Ce sera la seule partie “dynamique” du site.

Voici les parties importantes de comments/submit.php (basé sur la version de Jekyll::StaticComments) :

<?php
$DATE_FORMAT = "Y-m-d H:i:sP";
$EMAIL_ADDRESS = "your@email";
$SUBJECT = "Nouveau commentaire";
$COMMENT_SENT = "sent.html";

$msg = "post_id: " . $_POST["post_id"] . "\n";
$msg .= "email: " . $_POST["email"] . "\n";
$msg .= "---\n";
$msg .= "- id: ?\n";
$msg .= "  author: " . $_POST["name"] . "\n";
if ($_POST["url"] !== '')
{
    $msg .= "  author-url: " . $_POST["url"] . "\n";
}
$msg .= "  date: " . date($DATE_FORMAT) . "\n";
$msg .= "  contents: |\n" . $_POST["comment"];

$headers = "From: $EMAIL_ADDRESS\n";
$headers .= "Content-Type: text/plain; charset=utf-8";

if (mail($EMAIL_ADDRESS, $SUBJECT, $msg, $headers))
{
    include $COMMENT_SENT;
}
else
{
    echo "Le commentaire n'a pas pu être envoyé.";
}

Quand un commentaire est envoyé avec succès, la page comments/sent.html est affichée à l’utilisateur.

Ainsi, lorsqu’un commentaire est posté, je reçois un mail :

post_id: /2017/01/commentaires-statiques-avec-jekyll
email: my@email
---
- id: ?
  author: ®om
  author-url: http://blog.rom1v.com
  date: 2017-01-09 19:27:10+01:00
  contents: |
Ceci est un test.

J’ai d’ailleurs ajouté une règle procmail pour que ces mails arrivent dans un dossier dédié.

Je peux alors copier le contenu dans le .yaml correspondant, formatter le commentaire (entre autres l’indenter de 4 espaces, ce qu’on pourrait automatiser), et le commiter.

Résumé

Une fois mis en place, vous devriez donc avoir les fichiers suivants :

  • _data/comments-*.yaml
  • _include/comments.html
  • comments/submit.php
  • comments/sent.html

Conclusion

Je souhaitais depuis longtemps migrer vers un moteur de blog statique, qui correspond davantage à ma façon d’écrire des articles, et offre beaucoup d’avantages (légèreté, sécurité, maintenance…).

Je suis très content d’y être parvenu sans perdre les commentaires ni la possibilité d’en poster de nouveaux.

Certes, la validation est très manuelle, mais c’est le prix à payer pour avoir des commentaires statiques. Pour un blog avec une fréquence de commentaires assez faible, je pense que ce n’est pas très gênant.

Commentaires

Salut,

Je découvre ton blog et ta solution pour les commentaires m’intéresse vraiment. Malheureusement je n’utilise pas Jekyll mais Hugo alors je vais essayer de bidouiller un peu ça pour qu’il marche sur mon blog.

En tout cas bravo car c’est d’une simplicité remarquable.

Salut!

J’utilisais aussi Jekyll::StaticComments jusque récemment. Mais maintenant j’utilise Staticman (enfin jusqu’à ce qu’il se mette à planter x) ) Qui est plutôt sympa et assez rapide à mettre en place

Sinon je suis ravi de découvrir ton blog, je vois dans la liste d’articles qu’il y a une poignée de sujets qui m’intéressent bien

Bonne journée!

Salut,

Je viens de voir que tu as mis en place des méthodes anti spam, j’allais justement revenir CERS toi car j’ai copié ton système de commentaire pour mon blog et depuis quelques jours je reçois une dizaine de spam par jour.

Je vais regarder de plus près tes méthodes, elles marchent bien ?

®om

Depuis ce commit, l’anti-spam est très efficace. Il n’y a que 2 spams qui sont passés en plus d’un mois (et c’est sans doute parce que la réponse demandée tient sur 1 seul caractère).

Merci, j’ai mis ça en place, plus qu’à voir si le vendeur de viagra et d’antidépresseur arrête de m’embêter :)

Les commentaires sont fermés.