From 67a20df2cc07021309f09a1e3fd60cb61b112ce8 Mon Sep 17 00:00:00 2001 From: Antoine M Date: Thu, 28 May 2026 17:50:27 +0200 Subject: [PATCH] FEATURE Big toolbar refactor to handle share like and cita buttons directly from carhop theme --- includes/api.php | 54 ++++++ resources/css/base/links.css | 2 +- resources/js/app.ts | 2 - resources/js/singles/like-button.ts | 243 +++++++++++++++++++++++++++ resources/js/singles/share-button.ts | 36 ++++ resources/js/singles/singles.ts | 6 + single-activites.php | 2 +- single.php | 2 +- 8 files changed, 342 insertions(+), 5 deletions(-) create mode 100644 resources/js/singles/like-button.ts create mode 100644 resources/js/singles/share-button.ts diff --git a/includes/api.php b/includes/api.php index 602e523..8295927 100644 --- a/includes/api.php +++ b/includes/api.php @@ -19,6 +19,18 @@ add_action('rest_api_init', function () { 'callback' => 'build_fonds_archives_posts_by_letter', 'permission_callback' => '__return_true', )); + + /* ---------------- + INTERACTIONS ROUTES + -----------------*/ + + // ################ LIKE POST ################ + + register_rest_route('carhop-datas/v1/interactions', '/posts/like', array( + 'methods' => 'POST', + 'callback' => 'carhop_like_post', + 'permission_callback' => '__return_true', + )); }); function carhop_posts_where_starts_with_letter($where, $query) @@ -217,3 +229,45 @@ function build_fonds_archives_posts_by_letter($request) return $response; } + + +function carhop_like_post($request) +{ + + $post_id = $request->get_param('post_id'); + + + if (!$post_id) { + return new WP_Error('post_id_required', 'Post ID is required', array('status' => 400)); + } + + + $post_id = intval($post_id); + + // Vérifier que le post existe + if (!get_post($post_id)) { + return new WP_Error('post_not_found', 'Post non trouvé', array('status' => 404)); + } + + + $likes_count = get_post_likes_count($post_id); + + // Incrémenter le compteur + $new_likes = $likes_count + 1; + + // Mettre à jour la meta + update_post_meta($post_id, 'likes_count', $new_likes); + + $response_data = array( + 'success' => true, + 'post_id' => $post_id, + 'likes_count' => $new_likes, + 'message' => 'Like ajouté avec succès' + ); + + $response = new WP_REST_Response($response_data); + + $response->set_status(200); + + return $response; +} diff --git a/resources/css/base/links.css b/resources/css/base/links.css index 495515d..1bc4304 100644 --- a/resources/css/base/links.css +++ b/resources/css/base/links.css @@ -1,6 +1,6 @@ .entry-content, .site-content { - a[target='_blank']:not(.page-header__cta) { + a[target='_blank']:not(.page-header__cta, .share-link) { @apply external-link; } } diff --git a/resources/js/app.ts b/resources/js/app.ts index e59b3ba..99bb39e 100644 --- a/resources/js/app.ts +++ b/resources/js/app.ts @@ -7,7 +7,6 @@ import { searchBarInit } from './search-bar'; import singlesInit from './singles/singles'; import archivesInit from './archives/archives'; import archivesFondsArchivesInit from './archives/archives-fonds-archives'; -import handleCiteButton from './singles/cite-button'; import titlesInit from './titles'; import { pageHeaderAnimationInit, @@ -24,7 +23,6 @@ window.addEventListener('load', function () { singlesInit(); archivesInit(); archivesFondsArchivesInit(); - handleCiteButton(); titlesInit(); pageHeaderAnimationInit(); chapterSectionAnimationInit(); diff --git a/resources/js/singles/like-button.ts b/resources/js/singles/like-button.ts new file mode 100644 index 0000000..83c7643 --- /dev/null +++ b/resources/js/singles/like-button.ts @@ -0,0 +1,243 @@ +// ============================================================================= +// TYPE DECLARATIONS +// ============================================================================= +declare var Notyf: any; + +// ============================================================================= +// UTILITY FUNCTIONS +// ============================================================================= + +function getPostId() { + const postId = document.querySelector('.page-single')?.getAttribute('data-post-id'); + if (!postId) return; + + return postId; +} + +function getPostType() { + const postType = document.querySelector('.page-single')?.getAttribute('data-post-type'); + if (!postType) return; + return postType; +} + +// ============================================================================= +// LIKE TRACKING FUNCTIONS +// ============================================================================= + +/** + * Clé localStorage pour stocker les posts likés + */ +const LIKED_POSTS_KEY = 'dynamiques_liked_posts'; + +/** + * Récupère la liste des posts likés depuis localStorage + */ +function getLikedPosts(): string[] { + try { + const likedPosts = localStorage.getItem(LIKED_POSTS_KEY); + return likedPosts ? JSON.parse(likedPosts) : []; + } catch (error) { + console.error('Erreur lecture localStorage:', error); + return []; + } +} + +/** + * Ajoute un post à la liste des posts likés + */ +function addLikedPost(postId: string): void { + try { + const likedPosts = getLikedPosts(); + if (!likedPosts.includes(postId)) { + likedPosts.push(postId); + localStorage.setItem(LIKED_POSTS_KEY, JSON.stringify(likedPosts)); + } + } catch (error) { + console.error('Erreur sauvegarde localStorage:', error); + } +} + +/** + * Vérifie si un post a déjà été liké + */ +function isPostLiked(postId: string): boolean { + const likedPosts = getLikedPosts(); + return likedPosts.includes(postId); +} + +// ============================================================================= +// LIKE API FUNCTIONS +// ============================================================================= + +async function likePost(postId: string): Promise { + // Vérifier si le post a déjà été liké + if (isPostLiked(postId)) { + console.log(`Post ${postId} déjà liké !`); + showAlreadyLikedMessage(); + return false; + } + + try { + const response = await fetch(`/wp-json/carhop-datas/v1/interactions/posts/like`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + post_id: postId, + }), + }); + const data = await response.json(); + + if (data.success) { + console.log(`Post ${postId} liké ! Nouveau total: ${data.likes_count} likes`); + + // Sauvegarder le like dans localStorage + addLikedPost(postId); + + // Mettre à jour l'interface utilisateur + updateLikeDisplay(data.likes_count); + updateLikeButtonState(); + + // Afficher message de succès + showLikeSuccessMessage(data.likes_count); + + return true; + } else { + console.error('Erreur API:', data); + return false; + } + } catch (error) { + console.error('Erreur lors du like:', error); + return false; + } +} + +// ============================================================================= +// UI UPDATE FUNCTIONS +// ============================================================================= + +function updateLikeDisplay(likesCount: number) { + // Mettre à jour l'affichage du compteur de likes dans l'interface + const likeCountElement = document.querySelector('.socials-buttons .likes-count'); + const likeButton = document.querySelector('.socials-buttons__button--like'); + + if (likeCountElement && likeButton) { + likeButton.setAttribute('data-likes-count', `${likesCount}`); + likeButton.classList.add('is-disabled'); + likeCountElement.textContent = `${likesCount}`; + } +} + +function updateLikeButtonState() { + const postId = getPostId(); + const likeButton = document.querySelector('.socials-buttons__button--like'); + if (!postId || !likeButton) return; + + const hasAlreadyLiked = isPostLiked(postId); + if (hasAlreadyLiked) { + const actionText = likeButton.querySelector('.button-action-text'); + if (actionText) { + actionText.textContent = 'Déjà liké'; + } + likeButton.setAttribute('title', 'Déjà liké !'); + return; + } + + if (!hasAlreadyLiked) { + likeButton.classList.remove('is-disabled'); + } +} + +function updateLikesCountIndicator() { + const likesCountIndicator = likeButton.querySelector('.likes-count'); + const likesCount = likeButton.getAttribute('data-likes-count'); + + if (likesCount && likesCountIndicator) { + likesCountIndicator.textContent = `${parseInt(likesCount) + 1}`; + } +} + +function showAlreadyLikedMessage() { + const notyf = new Notyf({ + duration: 3000, + ripple: false, + dismissible: true, + types: [ + { + type: 'error', + background: '#ff6b6b', + icon: { + className: 'notyf__icon--error', + tagName: 'i', + // Pas de text personnalisé pour garder l'icône d'erreur par défaut + }, + }, + ], + position: { + x: 'right', + y: 'top', + }, + }); + + notyf.error(' Vous avez déjà liké ce post ! ❤️'); +} + +function showLikeSuccessMessage(likesCount: number) { + const notyf = new Notyf({ + duration: 2000, + ripple: false, + dismissible: true, + types: [ + { + type: 'success', + background: '#10B981', + icon: { + className: 'notyf__icon--success', + tagName: 'i', + text: '❤️', + }, + }, + ], + position: { + x: 'right', + y: 'top', + }, + }); + + notyf.success( + `Merci pour votre like !
Total: ${likesCount} like${likesCount > 1 ? 's' : ''}`, + ); +} + +// ============================================================================= +// MAIN INITIALIZATION FUNCTION +// ============================================================================= + +export default function handleLikeButton(): void { + const likeButton: HTMLElement | null = document.querySelector( + '.socials-buttons .socials-buttons__button--like', + ); + const postType = getPostType(); + const postId = getPostId(); + if (!postType || !postId || !likeButton) return; + + updateLikeButtonState(); + + // Ajouter l'événement click + likeButton.addEventListener('click', async (e) => { + e.preventDefault(); // Empêcher comportement par défaut + + const hasAlreadyLiked = isPostLiked(postId); + if (hasAlreadyLiked) return showAlreadyLikedMessage(); + // Désactiver temporairement le bouton pour éviter les clics multiples + likeButton.style.pointerEvents = 'none'; + + const success = await likePost(postId); + + // Réactiver le bouton + setTimeout(() => { + likeButton.style.pointerEvents = 'auto'; + }, 1000); + }); +} diff --git a/resources/js/singles/share-button.ts b/resources/js/singles/share-button.ts new file mode 100644 index 0000000..cc03b61 --- /dev/null +++ b/resources/js/singles/share-button.ts @@ -0,0 +1,36 @@ +export default function handleShareButton() { + const shareButton = document.querySelector('.socials-buttons__button--share'); + if (!shareButton) return; + + shareButton.addEventListener('click', () => { + const isOpen = shareButton.classList.contains('is-open'); + if (!isOpen) { + shareButton.classList.add('is-open'); + } else { + shareButton.classList.remove('is-open'); + } + }); + handleCopyLinkButton(); +} + +function handleCopyLinkButton() { + const copyLinkButton = document.querySelector('.share-button--copy-link a'); + if (!copyLinkButton) return; + + copyLinkButton.addEventListener('click', (e) => { + e.preventDefault(); + const url = copyLinkButton.getAttribute('data-url'); + if (!url) return; + navigator.clipboard.writeText(url); + const notyf = new Notyf({ + duration: 4000, + ripple: false, + dismissible: true, + position: { + x: 'right', + y: 'top', + }, + }); + notyf.success('Lien copié !'); + }); +} diff --git a/resources/js/singles/singles.ts b/resources/js/singles/singles.ts index d77486a..9b4f2af 100644 --- a/resources/js/singles/singles.ts +++ b/resources/js/singles/singles.ts @@ -1,5 +1,11 @@ import { handlePostToolbar } from './post-toolbar.ts'; +import handleCiteButton from './cite-button.ts'; +import handleLikeButton from './like-button.ts'; +import handleShareButton from './share-button.ts'; export default function singles(): void { handlePostToolbar(); + handleCiteButton(); + handleLikeButton(); + handleShareButton(); } diff --git a/single-activites.php b/single-activites.php index 1d6255c..b023852 100644 --- a/single-activites.php +++ b/single-activites.php @@ -5,7 +5,7 @@ $postType = get_post_type(); ?> -
+
" data-article-id=""> +