Dans notre article précédent, nous avons développé la page d'accueil de notre site, en intégrant divers éléments essentiels à la navigation et à l'esthétique. Nous avons défini une image d'en-tête et un titre (avec un sous-titre) pour donner une identité claire au site, et mis en place une navigation responsive afin de faciliter l'expérience utilisateur sur tous les appareils.
Dans le but de mettre en avant le contenu du site, nous avons conçu une disposition en boîtes pour les articles, qui comprend une image, un titre, un résumé et d'autres informations pertinentes. Ces boîtes d'articles sont disposées de manière intuitive et esthétique sur la page d'accueil.
Enfin, nous avons prévu une pagination pour gérer les articles lorsqu'ils deviennent nombreux. Cette pagination permet de fractionner le contenu et de faciliter la lecture pour les utilisateurs, tout en conservant une organisation claire et une rapidité de chargement optimale de la page.
Avec cette structure bien pensée, notre page d'accueil offre aux visiteurs un aperçu attrayant et organisé du contenu du site, garantissant une navigation simple et efficace pour tous les utilisateurs.
Dans le présent article, on va prévoir le code pour afficher un article spécifique avec tout son contenu.
Le repository
Puisqu'on a créé un repository pour les articles, commençons par créer une fonction pour aller chercher dans la base un article à partir de son slug :
class PostRepository
{
...
public function getPostBySlug(string $slug): Post
{
return Post::with('user:id,name', 'category')->whereSlug($slug)->firstOrFail();
}
Post::with('user:id,name', 'category') : Utilise la méthode with pour charger les relations user et category de l'article. user:id,name signifie que seules les colonnes id et name de la table users seront chargées.
->whereSlug($slug) : Ajoute une condition pour filtrer les articles dont le slug correspond à la valeur de $slug.
->firstOrFail() : Récupère le premier article qui correspond à la condition. Si aucun article n'est trouvé, une exception ModelNotFoundException sera levée.
Un composant pour afficher un article
Pour l'affichage d'un article, créons un composant Volt :
php artisan make:volt posts/show --class
Pour la partie PHP ça va être simple :
<?php
use App\Models\Post;
use App\Repositories\PostRepository;
use Livewire\Volt\Component;
new class extends Component {
public Post $post;
public function mount($slug): void
{
$postRepository = new PostRepository();
$this->post = $postRepository->getPostBySlug($slug);
}
}; ?>
Voici la partie HTML :
<div>
@section('title', $post->seo_title ?? $post->title)
@section('description', $post->meta_description)
@section('keywords', $post->meta_keywords)
<div id="top" class="flex justify-end gap-4">
<x-popover>
<x-slot:trigger>
<x-button class="btn-sm"><a
href="{{ url('/category/' . $post->category->slug) }}">{{ $post->category->title }}</a></x-button>
</x-slot:trigger>
<x-slot:content class="pop-small">
@lang('Show this category')
</x-slot:content>
</x-popover>
</div>
<x-header title="{!! $post->title !!}" subtitle="{{ ucfirst($post->created_at->isoFormat('LLLL')) }} "
size="text-2xl sm:text-3xl md:text-4xl" />
<div class="relative items-center w-full py-5 mx-auto prose md:px-12 max-w-7xl">
@if ($post->image)
<div class="flex flex-col items-center mb-4">
<img src="{{ asset('storage/photos/' . $post->image) }}" />
</div>
<br>
@endif
<div class="text-justify">
{!! $post->body !!}
</div>
</div>
<br>
<hr>
<p>@lang('By ') {{ $post->user->name }}</p>
</div>
On prévoit la route :
Volt::route('/posts/{slug}', 'posts.show')->name('posts.show');
Et on a cette apparence :
On voit qu'il faut ajouter une petite traduction :
"By ": "Par ",
Le SEO
Les premières lignes du code HTML concernent les informations SEO :
@section('title', $post->seo_title ?? $post->title)
@section('description', $post->meta_description)
@section('keywords', $post->meta_keywords)
On renseigne trois variables avec les valeurs enregistrées dans la base. Mais on n'a pas encore prévu de les recevoir dans le layout, on va donc arranger ça (views/components/layout.blade.php) :
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', config('app.name'))</title>
<meta name="description" content="@yield('description')">
<meta name="keywords" content="@yield('keywords')">
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
À la génération, on vérifie que ça se passe correctement :
<title>Post 9</title>
<meta name="description" content="Quidem veniam excepturi aut similique suscipit mollitia totam.">
<meta name="keywords" content="quae,dicta,quasi">
La typographie
Le plugin prose de Tailwind est conçu pour fournir des styles typographiques par défaut pour les contenus textuels, comme les articles de blog, les documents, etc. Vous pouvez en voir une jolie démonstration ici. On va donc l'ajouter à notre projet.
Pour utiliser le plugin prose, on doit d'abord l'installer :
npm install -D @tailwindcss/typography
Ensuite le déclarer dans tailwind.config.js :
/** @type {import('tailwindcss').Config} */
module.exports = {
...
plugins: [
plugins: [require("@tailwindcss/typography"), require("daisyui")],
],
}
Une fois le plugin installé et configuré, on peut l'utiliser dans le HTML pour appliquer des styles typographiques par défaut. C'est ce que j'ai fait pour l'affichage de l'article en ajoutant juste la classe prose :
<div class="relative items-center w-full py-5 mx-auto prose md:px-12 max-w-7xl">
Le plugin prose est un outil puissant pour appliquer rapidement des styles typographiques par défaut à un contenu textuel. Il est facile à installer et à utiliser, et il offre aussi des options de personnalisation pour s'adapter à différentes tailles d'écran et thèmes.
Styliser le code
Même si le plugin prose met un peu en forme le codage, ce n'est pas suffisant pour un CMS qui doit en afficher de façon spécifique. Dans ce cas, il faut s'orienter vers une librairie spécialisée. L'une des plus performantes est sans doute prismjs. En fonction de vos besoins, vous pouvez créer deux fichiers (css et js) sur mesure à partir de cette page. Ensuite, vous les ajoutez ici :
Et évidemment on les charge dans le layout (views/components/layout.blade.php) :
...
<head>
...
<link rel="stylesheet" href="{{ asset('storage/css/prism.css') }}">
</head>
<body class="min-h-screen font-sans antialiased bg-base-200/50 dark:bg-base-200">
...
<script src="{{ asset('storage/scripts/prism.js') }}"></script>
</body>
</html>
Ainsi, on aura du cade bien présenté ! Exactement comme celui que vous pouvez voir dans cet article.
Les thèmes
Un avantage d'utiliser MaryUI est qu'il est fondé sur DaisyUI. Vous pouvez trouver sur le site de nombreux composants qui n'ont pas été repris par Mary. Je vous laisse explorer cette jolie bibliothèque. Daisy propose de nombreux thèmes dont voici un aperçu :
Il y en a pour tous les goûts...
Pour notre CMS, on va se contenter de proposer un classique clair/sombre. Il faut commencer par importer le mode dark de Tailwind :
export default {
...
darkMode: 'class',
}
Ensuite, on utilise le composant "Theme toggle" de Mary (navigation/navbar.blade.php) :
<x-theme-toggle title="{{ __('Toggle theme') }}" class="w-4 h-8" />
</x-slot:actions>
</x-nav>
Maintenant, avec un clic sur la petite lune (ou le soleil), on passe du mode clair au mode sombre. Vous pouvez aussi tester sur ce site qui a le même code.
Vous pouvez changer les thèmes qui correspondent à "clair" et "sombre", la documentation est ici.
Rechercher des articles
Une fonctionnalité indispensable, pour un CMS qui peut comporter des centaines d'articles, est de disposer d'un outil de recherche. On crée un nouveau composant pour disposer d'un formulaire de recherche dans la barre de navigation :
php artisan make:volt search --class
Avec ce code :
<?php
use Livewire\Attributes\Validate;
use Livewire\Volt\Component;
new class() extends Component {
#[Validate('required|string|max:100')]
public string $search = '';
public function save()
{
$data = $this->validate();
return redirect('/search/' . $data['search']);
}
};
?>
<div>
<form wire:submit.prevent="save">
<x-input placeholder="{{ __('Search') }}..." wire:model="search" clearable icon="o-magnifying-glass" />
</form>
</div>
On ajoute la traduction :
"Search...": "Rechercher...",
Et la route :
Volt::route('/search/{param}', 'index')->name('posts.search');
Vous remarquez que cette route renvoie sur le composant de la page d'accueil, ce qui est logique.
Dans le repository on ajoute une fonction pour cette recherche :
public function search(string $search): LengthAwarePaginator
{
return $this->getBaseQuery()
->latest()
->where(function ($query) use ($search) {
$query->where('body', 'like', "%{$search}%")->orWhere('title', 'like', "%{$search}%");
})
->paginate(config('app.pagination'));
}
Vous remarquez qu'on effectue la recherche au niveau du titre et de contenu.
->latest() : Cette méthode trie les résultats de la requête par ordre décroissant de la colonne de date (created_at), ce qui signifie que les enregistrements les plus récents apparaîtront en premier.
->where(function ($query) use ($search) { ... }) : Cette partie ajoute une condition de recherche à la requête. La fonction anonyme (function ($query) use ($search) { ... }) est utilisée pour encapsuler les conditions de recherche.
$query->where('body', 'like', "%{$search}%") : Cette ligne ajoute une condition pour rechercher des enregistrements où la colonne body contient le terme de recherche. Le % est un caractère générique en SQL qui signifie "zéro ou plusieurs caractères".
->orWhere('title', 'like', "%{$search}%") : Cette ligne ajoute une condition supplémentaire pour rechercher des enregistrements où la colonne title contient le terme de recherche. Le orWhere signifie que la condition précédente ou cette condition doit être vraie.
->paginate(config('app.pagination')) : Cette méthode pagine les résultats de la requête en utilisant la configuration de pagination définie dans le fichier de configuration de l'application (config('app.pagination')). Cela permet de diviser les résultats en pages pour une meilleure gestion des grandes quantités de données.
Dans le composant index de la page d'accueil, on appelle cette fonction :
...
public string $param = '';
public function mount(string $slug = '', string $param = ''): void
{
$this->param = $param;
if (request()->is('category/*')) {
$this->category = $this->getCategoryBySlug($slug);
}
}
...
public function getPosts(): LengthAwarePaginator
{
$postRepository = new PostRepository();
if (!empty($this->param)) {
return $postRepository->search($this->param);
}
return $postRepository->getPostsPaginate($this->category);
}
Il ne reste plus qu'à ajouter le formulaire dans la barre de navigation (navigation/navbar.blade.php) :
<x-theme-toggle title="{{ __('Toggle theme') }}" class="w-4 h-8" />
<livewire:search />
</x-slot:actions>
</x-nav>
Sur la page d'accueil, on prévoit un titre :
<div class="relative grid items-center w-full py-5 mx-auto md:px-12 max-w-7xl">
@if ($category)
<x-header title="{{ __('Posts for category ') }} {{ $category->title }}" size="text-2xl sm:text-3xl md:text-4xl" />
@elseif($param !== '')
<x-header title="{{ __('Posts for search ') }} '{{ $param }}'" size="text-2xl sm:text-3xl md:text-4xl" />
@endif
Et évidemment la traduction qui va avec :
"Posts for search ": "Articles pour la recherche ",
Et ça devrait fonctionner :
Conclusion
Nous avons bien avancé dans notre CMS. Nous savons à présent afficher des articles, changer le thème des pages, faire des recherches. Mais il nous reste encore beaucoup de travail ! Alors rendez-vous à la prochaine étape !
Pour vous simplifier la vie, vous pouvez charger le projet dans son état à l’issue de ce chapitre.
Par bestmomo
Aucun commentaire