Compare commits

...

5 Commits

Author SHA1 Message Date
Nonimart
6ab16f4f65 FEATURE Including searchbar and filterarticles functions
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-16 14:03:06 +02:00
Nonimart
f1c1a00fef FEEDBACK removing article title 2025-09-16 14:02:48 +02:00
Nonimart
e707843129 FEATURE Fixing & Refining search bar behaviour 2025-09-16 14:02:17 +02:00
Nonimart
8211ba7a6b RESPONSIVE FIX refining responsivity 2025-09-16 14:01:44 +02:00
Nonimart
a74cf0af10 FEATURE Implementing articles filtering 2025-09-16 14:01:21 +02:00
8 changed files with 256 additions and 32 deletions

View File

@ -6,18 +6,24 @@ add_action('rest_api_init', function () {
BUILDING ROUTES
-----------------*/
// ################ NEWS ################
// ################ Authors ################
// * BUILD MORE NEWS CARDS
register_rest_route('dynamiques-datas/v1/build', '/revue/authors', array(
'methods' => 'GET',
'callback' => 'build_revue_authors',
'permission_callback' => '__return_true',
));
// ################ FILTER ARTICLES ################
register_rest_route('dynamiques-datas/v1/build', '/articles', array(
'methods' => 'GET',
'callback' => 'build_articles',
'permission_callback' => '__return_true',
));
});
// ################ NEWS ################
// ################ REVUE AUTHORS ################
function build_revue_authors($request)
{
@ -40,3 +46,95 @@ function build_revue_authors($request)
return $response;
}
// ################ FILTER ARTICLES ################
function build_articles($request)
{
$etiquette = esc_html($request->get_param('etiquette'));
$auteur = esc_html($request->get_param('auteur'));
$sort_by = esc_html($request->get_param('sort_by'));
$recherche = esc_html($request->get_param('recherche'));
// Construire les arguments de la query WordPress
$args = array(
'post_type' => 'articles',
'posts_per_page' => -1,
);
// Gestion du tri
switch ($sort_by) {
case 'date_desc':
$args['orderby'] = 'date';
$args['order'] = 'DESC';
break;
case 'date_asc':
$args['orderby'] = 'date';
$args['order'] = 'ASC';
break;
case 'title_asc':
$args['orderby'] = 'title';
$args['order'] = 'ASC';
break;
default:
$args['orderby'] = 'date';
$args['order'] = 'DESC';
}
// Filtre par étiquette (taxonomie)
if (!empty($etiquette) && $etiquette != '1') {
$args['tax_query'] = array(
array(
'taxonomy' => 'etiquettes',
'field' => 'term_id',
'terms' => $etiquette,
),
);
}
// Filtre par auteur
if (!empty($auteur) && $auteur != '1') {
$args['meta_query'] = array(
array(
'key' => 'authors', // Ajustez selon votre structure
'value' => $auteur,
'compare' => 'LIKE'
)
);
}
// Recherche par mot-clé
if (!empty($recherche)) {
$args['s'] = $recherche;
}
$articles = new WP_Query($args);
ob_start();
if ($articles->have_posts()) :
while ($articles->have_posts()) : $articles->the_post();
get_template_part('template-parts/articles/card-article', null, array(
'date' => get_the_date(),
'image' => get_the_post_thumbnail_url(),
'link' => get_the_permalink(),
'ID' => get_the_ID()
));
endwhile;
else :
echo '<p>Aucun article trouvé.</p>';
endif;
wp_reset_postdata();
$html_template = ob_get_clean();
$response_data = array(
'html_template' => $html_template,
'post_count' => $articles->found_posts,
'query_args' => $args, // Pour debug
);
$response = new WP_REST_Response($response_data);
$response->set_status(200);
return $response;
}

View File

@ -1,5 +1,5 @@
.post-header {
@apply bg-primary text-white py-32;
@apply bg-primary text-white py-32 px-2 lg:px-4 md:px-8;
h1.post-header__title,
h2.post-header__title {
@ -7,10 +7,13 @@
line-height: 1.2;
}
&__inner {
@apply container mx-auto grid gap-24;
@apply mx-auto grid gap-24;
@screen xl {
@apply container;
}
&--has-thumbnail {
@screen lg {
grid-template-columns: 1fr 4fr;
grid-template-columns: 4fr 1fr;
}
}
&--no-thumbnail {
@ -21,9 +24,14 @@
@apply bg-white text-primary;
}
.thumbnail-wrapper {
@apply order-2 lg:order-1 relative z-20;
aspect-ratio: 4/5;
@apply order-2 lg:order-1 relative z-20 h-fit;
padding: 1.2rem;
/* max-width: calc(70% - 40px);
@apply mx-auto; */
@screen lg {
transform: translateX(-40px);
}
&:before {
content: '';
@ -31,8 +39,9 @@
}
img {
aspect-ratio: 4/5;
/* max-height: 200px; */
@apply w-full h-full relative z-10;
@apply max-h-[200px] lg:max-h-full w-full h-full relative z-10;
@apply object-cover;
/* width: calc(100% - 2rem);
height: calc(100% - 2rem);

View File

@ -10,7 +10,7 @@
bottom-0
/* overflow-x-hidden */
transform translate-y-full;
@apply block;
@apply block overflow-x-hidden;
animation: translate-in 700ms forwards cubic-bezier(0, 0.51, 0.23, 0.99),
fade-in 600ms forwards ease-out;
@ -33,6 +33,23 @@
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes translate-out {
from {
transform: translateY(100%);
}
to {
transform: translateY(calc(100% - 100px));
}
}
&[closed] {
@apply hidden;
}
@ -101,5 +118,5 @@
}
body:has(.search-module[opened]) main {
filter: blur(2px) brightness(0.9);
filter: blur(2px) brightness(0.8);
}

View File

@ -1,14 +1,18 @@
import menuInit from './header';
import singlesInit from './singles/singles';
import SearchBar from './search-bar';
import { searchBarInit } from './search-bar';
import filterArticlesInit from './filter-articles';
window.addEventListener('DOMContentLoaded', function () {
menuInit();
singlesInit();
searchBarInit();
filterArticlesInit();
// Initialiser la barre de recherche
const searchBar = new SearchBar();
// // Initialiser la barre de recherche
// const searchBar = searchBarInit();
// Optionnel : Exposer l'instance globalement pour un accès externe
(window as any).searchBar = searchBar;
// // Optionnel : Exposer l'instance globalement pour un accès externe
// (window as any).searchBar = searchBar;
});

View File

@ -0,0 +1,48 @@
export default function filterArticlesInit() {
const toolbar = document.querySelector('.post-grid__toolbar');
if (!toolbar) return;
const postGridToolbarActions = toolbar.querySelector('.post-grid__toolbar-actions');
const etiquettesSelect = toolbar.querySelector('select[name="etiquettes"]') as HTMLSelectElement;
const auteursSelect = toolbar.querySelector('select[name="auteurs"]') as HTMLSelectElement;
const sortBySelect = toolbar.querySelector('select[name="sort_by"]') as HTMLSelectElement;
const rechercheInput = toolbar.querySelector('.search-bar input') as HTMLInputElement;
async function hydrateArticles() {
const etiquetteValue = etiquettesSelect.value;
const auteurValue = auteursSelect.value;
const sortByValue = sortBySelect.value;
const rechercheValue = rechercheInput.value;
console.table({
etiquettes: etiquetteValue,
auteurs: auteurValue,
sort_by: sortByValue,
recherche: rechercheValue,
});
try {
const response = await fetch(
`/wp-json/dynamiques-datas/v1/build/articles?etiquette=${etiquetteValue}&auteur=${auteurValue}&sort_by=${sortByValue}&recherche=${rechercheValue}`
// `/wp-json/dynamiques-datas/v1/build/articles?etiquettes=${etiquettesSelect.value}&auteurs=${auteursSelect.value}&sort_by=${sortBySelect.value}&recherche=${rechercheInput.value}`
);
const data = await response.json();
console.log(data);
const articlesContainer = document.querySelector('.post-grid__list');
if (!articlesContainer) return;
articlesContainer.innerHTML = data.html_template;
} catch (error) {
console.error('Erreur lors de la récupération des articles:', error);
}
}
// Écouter les changements sur les selects
postGridToolbarActions.addEventListener('change', (e) => {
hydrateArticles();
});
rechercheInput.addEventListener('input', (e) => {
hydrateArticles();
});
}

View File

@ -5,34 +5,47 @@ interface SearchBarOptions {
export default class SearchBar {
private searchBar: HTMLDivElement | null;
private searchButton: HTMLButtonElement | null;
private searchButtons: NodeListOf<HTMLButtonElement> | null;
private searchInput: HTMLInputElement | null;
private isOpen: boolean = false;
private lastScrollY: number = 0;
private activeButton: HTMLButtonElement | null = null;
constructor(options: SearchBarOptions = {}) {
this.searchBar = document.querySelector('#search-module') as HTMLDivElement;
this.searchButton = document.querySelector(
this.searchButtons = document.querySelectorAll(
'.tools-container .search-button'
) as HTMLButtonElement;
) as NodeListOf<HTMLButtonElement>; // Il y a 2 boutons de recherche (1 mobile; 1 desktop)
this.searchInput = document.querySelector(
'.search-module__search-form__input'
) as HTMLInputElement;
this.init();
}
private init(): void {
if (!this.searchBar || !this.searchButton) return;
if (!this.searchBar || !this.searchButtons) return;
// Initialiser l'état
this.isOpen = this.searchBar.hasAttribute('opened');
this.lastScrollY = window.scrollY;
this.updateAriaHidden();
// Ajouter les event listeners
this.searchButton.addEventListener('click', this.toggle.bind(this));
this.searchButtons.forEach((button) =>
button.addEventListener('click', (event) => this.toggle(event))
);
this.searchBar.addEventListener('transitionend', this.handleTransitionEnd.bind(this));
document.addEventListener('keydown', this.handleKeyDown.bind(this));
window.addEventListener('scroll', this.handleScroll.bind(this));
}
private toggle(): void {
private toggle(event: Event): void {
if (!this.searchBar) return;
// Stocker le bouton qui a été cliqué
this.activeButton = event.currentTarget as HTMLButtonElement;
this.isOpen = !this.isOpen;
if (this.isOpen) {
@ -48,14 +61,42 @@ export default class SearchBar {
this.searchBar.removeAttribute('closed');
this.searchBar.setAttribute('opened', '');
this.isOpen = true;
// Focus automatique sur le champ de recherche après un petit délai pour laisser l'animation commencer
setTimeout(() => {
if (this.searchInput && this.isOpen) {
this.searchInput.focus();
}
}, 100);
}
public close(): void {
if (!this.searchBar) return;
this.searchBar.setAttribute('closed', '');
// Défocus l'input de recherche
if (this.searchInput) {
this.searchInput.blur();
}
this.searchBar.setAttribute('closing', '');
this.searchBar.removeAttribute('opened');
this.isOpen = false;
// Écouter la fin de l'animation de fermeture (on attend la plus longue: translate-out 800ms)
const handleAnimationEnd = (event: AnimationEvent) => {
if (event.animationName === 'translate-out') {
this.searchBar?.removeAttribute('closing');
this.searchBar?.setAttribute('closed', '');
this.searchBar?.removeEventListener('animationend', handleAnimationEnd);
// Refocus sur le bouton qui avait ouvert la barre après la fermeture
if (this.activeButton) {
this.activeButton.focus();
}
}
};
this.searchBar.addEventListener('animationend', handleAnimationEnd);
}
private handleTransitionEnd(): void {
@ -68,10 +109,16 @@ export default class SearchBar {
}
}
private handleScroll(): void {
if (!this.isOpen) return;
this.close();
}
private updateAriaHidden(): void {
if (!this.searchBar) return;
if (this.searchBar.hasAttribute('closed')) {
if (this.searchBar.hasAttribute('closed') || this.searchBar.hasAttribute('closing')) {
this.searchBar.setAttribute('aria-hidden', 'true');
} else {
this.searchBar.setAttribute('aria-hidden', 'false');
@ -83,10 +130,13 @@ export default class SearchBar {
}
public destroy(): void {
if (!this.searchBar || !this.searchButton) return;
if (!this.searchBar || !this.searchButtons) return;
this.searchButton.removeEventListener('click', this.toggle.bind(this));
// Note: Les event listeners anonymes ne peuvent pas être supprimés facilement
// Dans un vrai projet, il faudrait stocker les références des fonctions
this.searchBar.removeEventListener('transitionend', this.handleTransitionEnd.bind(this));
document.removeEventListener('keydown', this.handleKeyDown.bind(this));
window.removeEventListener('scroll', this.handleScroll.bind(this));
}
}

View File

@ -48,9 +48,9 @@ $thematiques = get_terms(array(
</select>
<select name="sort_by">
<option value="recent" selected><?php _e('Numéros récents en premier', 'dynamiques'); ?></option>
<option value="oldest"><?php _e('Numéros anciens en premier', 'dynamiques'); ?></option>
<option value="alphabetical"><?php _e('Par ordre alphabétique', 'dynamiques'); ?></option>
<option value="date_desc" selected><?php _e('Numéros récents en premier', 'dynamiques'); ?></option>
<option value="date_asc"><?php _e('Numéros anciens en premier', 'dynamiques'); ?></option>
<option value="title_asc"><?php _e('Par ordre alphabétique', 'dynamiques'); ?></option>
</select>
</div>

View File

@ -2,7 +2,7 @@
$postId = $args['postId'];
$isArticle = is_singular('articles');
$componentTitle = $isArticle ? 'Auteur·e·s de l\'article' : 'Auteur·e·s de la revue';
$articleTitle = get_the_title($postId);
$authors = [];
if ($isArticle) {
@ -16,9 +16,7 @@ if (empty($authors)) return;
?>
<section class="authors-list">
<h3 class="authors-list__title"><?php echo $componentTitle; ?></h3>
<?php if ($isArticle && $articleTitle) : ?>
<p class="authors-list__article-title"><?php echo $articleTitle; ?></p>
<?php endif; ?>
<?php foreach ($authors as $authorID) : ?>
<?php get_template_part(
'template-parts/authors/card-author',