Laravel

Un framework qui rend heureux

Voir cette catégorie
Vers le bas
Voir cette série
Mon CMS - Les commentaires (2/2)
Samedi 31 août 2024 11:27

Dans le précédent article, nous avons posé les bases pour l'affichage des commentaires. Nous sommes maintenant en mesure de charger les commentaires avec les informations de leurs auteurs. Actuellement, nous nous contentons d'afficher le nombre total de commentaires de l'article et un bouton pour les afficher. Nous allons à présent implémenter la fonctionnalité permettant d'afficher les commentaires de manière dynamique et hiérarchique.

Notre objectif est de créer un système d'affichage de commentaires qui soit :

  • Efficace en termes de performance
  • Facile à naviguer pour l'utilisateur
  • Capable de gérer une structure hiérarchique de commentaires

Voici les étapes que nous allons suivre pour implémenter cette fonctionnalité :

  • Affichage des commentaires de premier niveau
    • Au clic sur le bouton principal, nous afficherons uniquement les commentaires de premier niveau (sans parent).
    • Cela permet un chargement rapide et évite de surcharger l'interface utilisateur.
  • Boutons pour les commentaires enfants
    • Pour chaque commentaire de premier niveau, nous ajouterons un bouton permettant d'afficher ses commentaires enfants.
    • Ce bouton n'apparaîtra que si le commentaire a effectivement des réponses.
  • Chargement récursif
    • Lorsqu'un utilisateur clique sur le bouton pour afficher les réponses à un commentaire, nous chargerons dynamiquement ces réponses.
    • Ce processus sera récursif, permettant ainsi d'afficher des conversations imbriquées à plusieurs niveaux.
  • Optimisation des performances
    • Nous implémenterons un système de chargement paresseux (lazy loading) pour ne charger que les commentaires nécessaires.
    • Cela évitera de surcharger le serveur et le navigateur avec des données non immédiatement nécessaires.

Avantages de cette approche :

  • Amélioration des performances : En ne chargeant que les commentaires nécessaires, nous réduisons la charge sur le serveur et accélérons le temps de chargement initial de la page.
  • Meilleure expérience utilisateur : Les utilisateurs peuvent facilement naviguer dans la structure des commentaires sans être submergés par une longue liste de tous les commentaires à la fois.
  • Scalabilité : Cette approche permet de gérer efficacement des articles avec un grand nombre de commentaires et de réponses imbriquées.
  • Flexibilité : Le système peut être aisément étendu pour inclure des fonctionnalités supplémentaires, comme le tri des commentaires ou la pagination.

En suivant cette approche, nous créerons un système de commentaires dynamique et efficace qui améliorera considérablement l'interactivité et l'engagement des utilisateurs sur notre CMS.

Le formulaire

Nous avons besoin d'un formulaire qui sera commun à toutes les situations : commentaire de premier niveau, réponse, modification. On va donc créer un composant dédié :

php artisan make:volt posts/comment-form --class

Avec ce code :

@if ($showForm)
    <x-card title="{{ $formTitle }}" shadow="hidden" class="!p-0">
        <x-form wire:submit="{{ $formAction }}" class="mb-4">
            <x-textarea wire:model="message" hint="{{ __('Max 10000 chars') }}" rows="5" inline />
            <x-slot:actions>
                @if ($formAction === 'updateComment')
                    <x-button label="{{ __('Cancel') }}" wire:click="toggleModifyForm(false)"
                        class="btn-ghost" spinner />
                @endif
                <x-button label="{{ __('Save') }}" class="btn-primary" type="submit" spinner="save" />
            </x-slot:actions>
        </x-form>
    </x-card>
@else
    <div class="mb-4">{!! $message !!}</div>
@endif

On prévoit une variable $showForm pour rendre le formulaire optionnel. Pour un commentaire affiché, le formulaire ne doit apparaître que si on lui répond ou si on le modifie.

On verra plus en détail ce code plus loin.

Gravatar

Gravatar (Globally Recognized Avatar) présente plusieurs avantages importants pour les utilisateurs du web :

  • Identité visuelle cohérente : Gravatar permet de maintenir une identité visuelle cohérente à travers différents sites web et plateformes. Votre avatar vous suit partout où vous laissez des commentaires ou interagissez en ligne, ce qui facilite votre reconnaissance par les autres utilisateurs.
  • Professionnalisme et crédibilité : l'utilisation d'un Gravatar ajoute un aspect professionnel à vos interactions en ligne. Avoir une image de profil cohérente et soignée augmente votre crédibilité et encourage les autres à vous prendre au sérieux.
  • Amélioration de la visibilité et de la notoriété : au fil du temps, les gens commenceront à reconnaître votre Gravatar sur différents sites, ce qui accroît votre visibilité et votre notoriété en ligne. Cela peut être particulièrement bénéfique pour les blogueurs, les professionnels et les entreprises cherchant à établir leur présence sur le web.
  • Facilitation des contacts et du réseautage : Gravatar facilite la création de liens avec d'autres utilisateurs en ligne. Il permet aux gens de vous identifier facilement et peut inciter les interactions et le réseautage professionnel.
  • Simplicité d'utilisation : une fois configuré, Gravatar fonctionne automatiquement sur de nombreux sites web. Vous n'avez pas besoin de télécharger votre avatar à chaque fois que vous interagissez sur un nouveau site.
  • Personnalisation et gestion centralisée : Gravatar vous permet de gérer simplement votre avatar depuis un seul endroit. Vous pouvez associer différentes images à différentes adresses e-mail et mettre à jour votre profil de manière centralisée.

En résumé, Gravatar offre une solution simple et efficace pour maintenir une présence en ligne cohérente et professionnelle, tout en améliorant votre visibilité et en facilitant les interactions avec d'autres utilisateurs du web.

On va donc installer un package pour gérer facilement les Gravatars pour les commentaires :

composer require creativeorange/gravatar ~1.0

Grâce à la façade, la récupération de l'image correspondant à une adresse e-mail sera très simple :

Gravatar::get('email@example.com');

Notification

Il sera judicieux de prévenir l'auteur d'un article de l'ajout d'un commentaire. Le système de notification par email de Laravel offre une approche simple et puissante pour envoyer des notifications aux utilisateurs. Utilisez la commande Artisan pour générer une nouvelle classe de notification :

php artisan make:notification CommentCreated

Chaque notification contient une méthode via() qui spécifie les canaux de livraison (email dans ce cas) et une méthode toMail() qui définit le contenu de l'email :

<?php

namespace App\Notifications;

use App\Models\Comment;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class CommentCreated extends Notification
{
	use Queueable;

	public Comment $comment;

	public function __construct(Comment $comment)
	{
		$this->comment = $comment;
	}

	public function via(object $notifiable): array
	{
		return ['mail'];
	}

	public function toMail(object $notifiable): MailMessage
	{
		return (new MailMessage())
			->subject(__('A comment has been created on your post'))
			->line(__('A comment has been created on your post') . ' "' . $this->comment->post->title . '" ' . __('by') . ' ' . $this->comment->user->name . '.')
			->lineIf(!$this->comment->user->valid, __('This comment is awaiting moderation.'))
			->action(__('Manage this comment'), "#");
	}

	public function toArray(object $notifiable): array
	{
		return [
		];
	}
}

Il faudra transmettre le commentaire concerné à cette notification. D'autre part on a besoin de quelques traductions :

"A comment has been created on your post": "Un commentaire a été ajouté à votre article",
"This comment is awaiting moderation.": "Ce commentaire est en attente de modération.",
"Manage this comment": "Gérer ce commentaire",
""by": "par",

On ne connait pas encore le lien pour aller gérer le commentaire, on l'ajoutera plus tard.

Commentaire de premier niveau

On a vu qu'on doit distinguer les commentaires de premier niveau et les suivants. On doit avoir un formulaire toujours disponible pour ces commentaires de premier niveau. D'autre part, ces commentaires n'ont pas encore d'enfant puisqu'ils n'existent pas. Pour ces raisons, on va y consacrer un composant spécifique :

php artisan make:volt posts/commentBase --class

Au niveau des propriétés, on aura besoin :

  • de l'identifiant de l'article
  • d'une instance de la classe Comment pour mémoriser le nouveau commentaire
  • une propriété $message pour contenir le nouveau texte du message
  • de variables d'intendance pour afficher ou cacher le formulaire selon la situation, afficher un message optionnel pour indiquer à un utilisateur qui a rédigé son nouveau message que celui-ci devra être validé par un administrateur avant d'apparaître effectivement.

Pour les fonctions, on aura :

  • mount : au montage du composant, on va récupérer et conserver l'identifiant de l'article
  • createComment : à la soumission du formulaire, cette fonction créera le commentaire dans la base
  • updateComment : à la soumission du formulaire, cette fonction modifiera le commentaire dans la base
  • toggleModifyForm : un commutateur pour afficher ou cacher le formulaire
  • deleteComment : pour supprimer le commentaire dans la base

Le PHP

<?php

use App\Models\{ Comment, Post };
use App\Notifications\CommentCreated;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Livewire\Attributes\Validate;
use Livewire\Volt\Component;

new class() extends Component {
	public int $postId;
	public ?Comment $comment    = null;
	public bool $showCreateForm = true;
	public bool $showModifyForm = false;
	public bool $alert          = false;

	#[Validate('required|max:10000')]
	public string $message = '';

	public function mount($postId): void
	{
		$this->postId = $postId;
	}

	public function createComment(): void
	{
		$data = $this->validate();

		if (!Auth::user()->valid) {
			$this->alert = true;
		}

		$post = Post::select('id', 'title', 'user_id')->with('user')->findOrFail($this->postId);		

		$this->comment = Comment::create([
			'user_id' => Auth::id(),
			'post_id' => $this->postId,
			'body'    => $this->message,
		]);

		$post->user->notify(new CommentCreated($this->comment));

		$this->message = $data['message'];
	}

	public function updateComment(): void
	{
		$data = $this->validate();

		$this->comment->body = $data['message'];
		$this->comment->save();

		$this->toggleModifyForm(false);
	}

	public function toggleModifyForm(bool $state): void
	{
		$this->showModifyForm = $state;
	}

	public function deleteComment(): void
	{
		$this->comment->delete();

		$this->comment = null;
		$this->message = '';
	}
}; ?>

Quelques explications pour la fonction d'enregistrement d'un nouveau commentaire :

On utilise la méthode validate sur l'objet courant ($this) qui valide les données d'entrée et retourne les données validées dans la variable $data.
On vérifie si l'utilisateur authentifié (Auth::user()) a une propriété valid qui est fausse. Si c'est le cas, une alerte est définie en mettant $this->alert à true.
On récupère le post à partir de la base de données en utilisant l'ID du post ($this->postId). On sélectionne uniquement les colonnes id, title, et user_id, et on charge également l'utilisateur associé au post (with('user')). Si le post n'est pas trouvé, une exception est levée (findOrFail), mais ça serait surprenant.
On crée un nouveau commentaire en utilisant les données fournies. Le commentaire est associé à l'utilisateur authentifié (Auth::id()), au post ($this->postId), et contient le message ($this->message). Le commentaire créé est stocké dans la propriété $this->comment.
On envoie une notification à l'utilisateur qui a créé le post ($post->user). La notification est une instance de la classe CommentCreated, qui prend le commentaire nouvellement créé comme argument.
On met à jour la propriété $this->message avec le message validé provenant de $data.

Le HTML

<div class="flex flex-col mt-4">
    @if ($this->comment)

        @if ($alert)
            <x-alert title="{!! __('This is your first comment') !!}" description="{!! __('It will be validated by an administrator before it appears here') !!}" icon="o-exclamation-triangle"
                class="alert-warning" />
        @else
            <div class="flex flex-col justify-between mb-4 md:flex-row">
                <x-avatar :image="Gravatar::get(Auth::user()->email)" class="!w-24">
                    <x-slot:title class="pl-2 text-xl">
                        {{ Auth::user()->name }}
                    </x-slot:title>
                    <x-slot:subtitle class="flex flex-col gap-1 pl-2 mt-2 text-gray-500">
                        <x-icon name="o-calendar" label="{{ $comment->created_at->diffForHumans() }}" />
                        <x-icon name="o-chat-bubble-left"
                            label="{{ $comment->user->comments_count }} {{ __(' comments') }}" />
                    </x-slot:subtitle>
                </x-avatar>

                <div class="flex flex-col mt-4 space-y-2 lg:mt-0 lg:flex-row lg:items-center lg:space-y-0 lg:space-x-2">
                    <x-button label="{{ __('Modify') }}" wire:click="toggleModifyForm(true)"
                        class="btn-outline btn-sm" />
                    <x-button label="{{ __('Delete') }}" wire:click="deleteComment()"
                        wire:confirm="{{ __('Are you sure to delete this comment?') }}"
                        class="btn-outline btn-error btn-sm" />
                </div>
            </div>

            @include('livewire.posts.comment-form', ['formTitle' => __('Update your comment'), 'formAction' => 'updateComment', 'showForm' => $showModifyForm, 'message' => $comment->body])

        @endif

    @else
        @include('livewire.posts.comment-form', ['formTitle' => __('Leave a comment'), 'formAction' => 'createComment', 'showForm' => true, 'message' => ''])
    @endif

</div>

Quelques traductions :

"This is your first comment": "C'est votre premier commentaire",
"It will be validate by an administrator before it appears here": "Il sera valide par un administrateur avant qu'il ne soit affiché ici",
"Leave a comment": "Laissez un commentaire",
"Your comment": "Votre commentaire",
"Update your comment": "Modifier votre commentaire",
"Are you sure to delete this comment?": "Etes-vous sûr de vouloir supprimer ce commentaire ?",
"Comments": "Commentaires",
"Modify": "Modifier",
"1 comment": "1 commentaire",
"comments": "commentaires",

Afficher le formulaire à la fin de l'article

Maintenant qu'on a notre formulaire de base pour les commentaires de premier niveau, on va modifier le composant d'affichage des articles (posts.show) pour l'intégrer. Pour le moment, on a juste affiché un bouton qui charge les commentaires sans les afficher, ce qu'on ne va d'ailleurs pas faire tout de suite parce qu'on n'a pas encore créé le composant pour le faire. On va se contenter d'afficher notre nouveau formulaire.

    ...

   <div id="bottom" class="relative items-center w-full py-5 mx-auto md:px-12 max-w-7xl">
        @if ($listComments)
            <x-card title="{{ __('Comments') }}" shadow separator>
                 Affichage des commentaires ici !
                @auth
                    <livewire:posts.commentBase :postId="$post->id" />
                @endauth
            </x-card>
        @else
            @if ($commentsCount > 0)
                <div class="flex justify-center">
                    <x-button label="{{ $commentsCount > 1 ? __('View comments') : __('View comment') }}"
                        wire:click="showComments" class="btn-outline" spinner />
                </div>
            @else
                @auth
                    <livewire:posts.commentBase :postId="$post->id" />
                @endauth
            @endif
        @endif
    </div>

</div>

On a plusieurs cas :

  • Il y a des commentaires chargés :
    • on les affiche (pour le moment, on ne le fait pas)
    • pour les utilisateurs connectés, on affiche le formulaire
  • il n'y a pas de commentaires chargés (situation au premier chargement de l'article)
    • on affiche le bouton pour les charger
    • pour les utilisateurs connectés, on affiche le formulaire

Pour un utilisateur connecté, on a :

Vérifiez que vous pouvez créer un commentaire :

Et que vous pouvez le modifier en utilisant le bouton. Et aussi de le supprimer !

Et évidemment un Email doit partir pour l'auteur de l'article, vous le trouvez dans les logs :

# Bonjour !

Un commentaire a été ajouté à votre article "Post 2" par User.

Gérer ce commentaire: #

Cordialement,
Mon CMS

Si vous avez des difficultés à cliquer sur le bouton "Gérer ce commentaire", copiez et collez l'URL ci-dessous
dans votre navigateur Web : [#](#)

© 2024 Mon CMS. Tous droits réservés.

Afficher les commentaires

Notification

Il sera judicieux de prévenir l'auteur d'un commentaire de l'ajout d'une réponse à son commentaire. Utilisez à nouveau la commande Artisan pour générer une nouvelle classe de notification :

php artisan make:notification CommentAnswerCreated

<?php

namespace App\Notifications;

use App\Models\Comment;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class CommentAnswerCreated extends Notification
{
	use Queueable;

	public Comment $comment;
	public function __construct(Comment $comment)
	{
		$this->comment = $comment;
	}

	public function via(object $notifiable): array
	{
		return ['mail'];
	}

	public function toMail(object $notifiable): MailMessage
	{
		return (new MailMessage())
			->subject(__('An answer has been created on your comment'))
			->line(__('An answer has been created on your comment') . ' "' . $this->comment->post->title . '" ' . __('by') . ' ' . $this->comment->user->name . '.')
			->action(__('Show this comment'), route('posts.show', $this->comment->post->slug));
	}

	public function toArray(object $notifiable): array
	{
		return [
		];
	}
}

On ajoute ces traductions :

"An answer has been created on your comment": "Une réponse a été apportée à votre commentaire",
"Show this comment": "Voir ce commentaire",

Un composant pour l'affichage des commentaires

Nous en arrivons à la partie la plus délicate : on doit afficher les commentaires de premier niveau et pour chacun d'eux donner la possibilité de le gérer pour son auteur (modifier, supprimer) et d'y répondre. Il faut aussi prévoir un bouton si des réponses existent pour les afficher.

On crée un nouveau composant Volt :

php artisan make:volt posts/comment --class

Au niveau des propriétés, on aura besoin :

  • d'une instance de la classe Comment pour mémoriser le nouveau commentaire
  • d'une collection $children pour les enfants éventuels
  • d'un entier $depth pour connaître la profondeur de commentaire (on limitera à 3)
  • une propriété $message pour contenir le nouveau texte du message
  • de variables d'intendance pour afficher ou cacher le formulaire selon la situation, afficher un message optionnel pour indiquer à un utilisateur qui a rédigé son nouveau message que celui-ci devra être validé par un administrateur avant d'apparaître effectivement.

Pour les fonctions :

  • mount : pour mémoriser le commentaire, la profondeur, le message et le nombre d'enfants
  • showAnswers : s'il y a des réponses, cette fonction pourra les charger 
  • toggleAnswerForm et toggleModifyForm : intendance pour afficher et cacher les formulaires
  • createAnswer : pour créer le commentaire de réponse dans la base
  • deleteComment : pour supprimer le commentaire

Le PHP

<?php

use App\Models\{ Comment, Reaction };
use App\Notifications\{CommentAnswerCreated, CommentCreated};
use Illuminate\Support\Collection;
use Livewire\Attributes\Validate;
use Livewire\Volt\Component;

new class() extends Component {
	public ?Comment $comment;
	public ?Collection $children;
	public bool $showAnswerForm = false;
	public bool $showModifyForm = false;
	public int $depth;
	public bool $alert = false;
    public int $children_count = 0;

	#[Validate('required|max:10000')]
	public string $message = '';

	public function mount($comment, $depth): void
	{
		$this->comment = $comment;
		$this->depth   = $depth;
		$this->message = strip_tags($comment->body);
        $this->children_count = $comment->children_count;
	}

	public function showAnswers(): void
	{
		$this->children = Comment::where('parent_id', $this->comment->id)
            ->with([
                'user' => function ($query) {
                    $query->select('id', 'name', 'email', 'role')->withCount('comments');
                },
            ])
			->withCount(['children' => function ($query) {
				$query->whereHas('user', function ($q) {
					$q->where('valid', true);
				});
			}])
			->get();

		$this->children_count = 0;
	}

	public function toggleAnswerForm(bool $state): void
	{
		$this->showAnswerForm = $state;
		$this->message        = '';
	}

	public function toggleModifyForm(bool $state): void
	{
		$this->showModifyForm = $state;
	}

	public function createAnswer(): void
	{
		$data              = $this->validate();
		$data['parent_id'] = $this->comment->id;
		$data['user_id']   = Auth::id();
		$data['post_id']   = $this->comment->post_id;
		$data['body']      = $this->message;

		$item = Comment::create($data);

		$item->save();

		$item->post->user->notify(new CommentCreated($item));
		$item->post->user->notify(new CommentAnswerCreated($item));

		$this->toggleAnswerForm(false);

		$this->showAnswers();
	}

	public function updateAnswer(): void
	{
		$data = $this->validate();

		$this->comment->body = $data['message'];
		$this->comment->save();

		$this->toggleModifyForm(false);
	}

	public function deleteComment(): void
	{
		$this->comment->delete();
		$this->childs  = null;
		$this->comment = null;
	} 

}; ?>

On retrouve un peu le code précédent avec quelques variantes.

Le HTML

<div>
    <style>
        @media (max-width: 768px) {
            .ml-0 { margin-left: 0rem; }
            .ml-3 { margin-left: 0.75rem; }
            .ml-6 { margin-left: 1.5rem; }
            .ml-9 { margin-left: 2.25rem; }
        }
        @media (min-width: 769px) {
            .ml-0 { margin-left: 0rem; }
            .ml-3 { margin-left: 3rem; }
            .ml-6 { margin-left: 6rem; }
            .ml-9 { margin-left: 9rem; }
        }
    </style>

    @if ($comment)
        <div class="flex flex-col mt-4 ml-{{ $depth * 3 }} lg:ml-{{ $depth * 3 }} border-2 border-gray-400 rounded-md p-2 selection:transition duration-500 ease-in-out shadow-md shadow-gray-500 hover:shadow-xl hover:shadow-gray-500" >

            <div class="flex flex-col justify-between mb-4 md:flex-row">
                <x-avatar :image="Gravatar::get($comment->user->email)" class="!w-24">
                    <x-slot:title class="pl-2 text-xl">
                        {{ $comment->user->name }}
                    </x-slot:title>
                    <x-slot:subtitle class="flex flex-col gap-1 pl-2 mt-2 text-gray-500">
                        <x-icon name="o-calendar" label="{{ $comment->created_at->diffForHumans() }}" />
                        <x-icon name="o-chat-bubble-left"  label="{{ $comment->user->comments_count == 0 ? '' : ($comment->user->comments_count == 1 ? __('1 comment') : $comment->user->comments_count . ' ' . __('comments')) }}" />
                    </x-slot:subtitle>
                </x-avatar>

                <div class="flex flex-col mt-4 space-y-2 lg:mt-0 lg:flex-row lg:items-center lg:space-y-0 lg:space-x-2">
                    @auth
                        @if (Auth::user()->name == $comment->user->name)
                            <x-button label="{{ __('Modify') }}" wire:click="toggleModifyForm(true)"
                                class="btn-outline btn-warning btn-sm" spinner />
                            <x-button label="{{ __('Delete') }}" wire:click="deleteComment()"
                                wire:confirm="{{ __('Are you sure to delete this comment?') }}"
                                class="mt-2 btn-outline btn-error btn-sm" spinner />
                        @endif
                        @if ($depth < 3)
                            <x-button label="{{ __('Answer') }}" wire:click="toggleAnswerForm(true)"
                                class="mt-2 btn-outline btn-sm" spinner />
                        @endif
                    @endauth
                </div>
            </div>

            @if(!$showModifyForm)
                <div class="mb-4">
                    {!! nl2br($comment->body) !!}
                </div>
            @endif
            @if ($showModifyForm || $showAnswerForm)
                <x-card :title="($showModifyForm ? __('Update your comment') : __('Your answer'))" shadow="hidden" class="!p-0">
                    <x-form :wire:submit="($showModifyForm ? 'updateAnswer' : 'createAnswer')" class="mb-4">
                        <x-textarea wire:model="message" :placeholder="($showAnswerForm ? __('Your answer') . ' ...' : '')" hint="{{ __('Max 10000 chars') }}" rows="5" inline />
                        <x-slot:actions>
                            <x-button label="{{ __('Cancel') }}" :wire:click="($showModifyForm ? 'toggleModifyForm(false)' : 'toggleAnswerForm(false)')"
                                class="btn-ghost" />
                            <x-button label="{{ __('Save') }}" class="btn-primary" type="submit" spinner="save" />
                        </x-slot:actions>
                    </x-form>
                </x-card>
            @endif

            @if ($alert)
                <x-alert title="{!! __('This is your first comment') !!}"
                    description="{{ __('It will be validated by an administrator before it appears here') }}"
                    icon="o-exclamation-triangle" class="alert-warning" />
            @endif

            @if($children_count > 0)
                <x-button label="{{ __('Show the answers') }} ({{ $children_count }})" wire:click="showAnswers" class="mt-2 btn-outline btn-sm" spinner />
            @endif

        </div>
    @endif

    @if($children)
        @foreach ($children as $child)
            <livewire:posts.comment :comment="$child" :depth="$depth + 1" :key="$child->id">
        @endforeach
    @endif

</div>

Une peu de traduction :

"Answer": "Répondre",
"Show the answers": "Afficher les réponses",

On va modifier le composant d'affichage des articles (posts.show) pour intégrer notre nouveau composant :

<div id="bottom" class="relative items-center w-full py-5 mx-auto md:px-12 max-w-7xl">
    @if ($listComments)            
        <x-card title="{{ __('Comments') }}" shadow separator>
            @foreach ($comments as $comment)
                @if (!$comment->parent_id)
                    <livewire:posts.comment :$comment :depth="0" :key="$comment->id" />
                @endif
            @endforeach

Maintenant les commentaires s'affichent :

Vous trouvez de plus jolis exemples dans ce blog où certains articles possèdent de nombreux commentaires. Par exemple dans cet article sur l'album photo, ma discussion avec Ronald. Il y a en plus un système de "likes" qu'on ajoutera plus tard.

Amusez-vous avec les commentaires pour voir si tout fonctionne bien et si les notification partent correctement.

Un peu de sécurité

Il ne vous a sans doute pas échapé qu'on affiche le contenu des commentaires avec une syntaxe plutôt permissive :

{!! nl2br($comment->body) !!}

nl2br($comment->body) :
   Cette partie du code prend le texte du commentaire ($comment->body) et applique la fonction nl2br pour convertir les sauts de ligne en balises <br>.

{!! nl2br($comment->body) !!} :
   Ensuite, le résultat de nl2br($comment->body) est affiché dans la vue sans échapper les caractères spéciaux HTML. Cela signifie que si le texte du commentaire contient des balises HTML, elles seront interprétées et rendues par le navigateur.

Utiliser {!! !!} peut être dangereux si le contenu n'est pas sécurisé, car cela peut permettre l'injection de code HTML ou JavaScript malveillant (XSS). On devrait s'assurer le contenu est sécurisé avant de l'afficher de cette manière. Pour le moment il ne l'est pas.

Le plus simple est d'utiliser HTML Purifier.

Surtout qu'il existe un package pour Laravel qu'on va se faire un plaisir d'installer :

composer require mews/purifier

Il suffit ensuite de prévoir ce simple code dans le modèle Comment :

...

use Mews\Purifier\Casts\CleanHtmlInput;

class Comment extends Model
{
    ...

	protected $casts = [
		'body' => CleanHtmlInput::class,
	];

Nos commentaires seront systématiquement nettoyés avant d'être enregistrés.

Conclusion

Nous avons maintenant des commentaires bien affichés et qui bénéficient de toutes les fonctionnalités attendues (détermination des enfants, modification, suppression, réponse...).

Pour vous simplifier la vie, vous pouvez charger le projet dans son état à l’issue de ce chapitre.



Par bestmomo

Aucun commentaire

Article précédent : Mon CMS - Les commentaires (1/2)
Article suivant : Mon CMS - Le profil