From 8f1f00108573c49250159f17cb6dce9b3bf4cf48 Mon Sep 17 00:00:00 2001 From: nekenny Date: Fri, 3 Apr 2026 01:13:09 +0500 Subject: [PATCH] feat: add content feed with AJAX filtering and load more on front page --- wp-content/themes/bitwms-child/functions.php | 1 + .../themes/bitwms-child/inc/ajax-filter.php | 78 +++++++++++ .../themes/bitwms-child/inc/enqueue.php | 22 +++- .../themes/bitwms-child/js/ajax-filter.js | 123 ++++++++++++++++++ wp-content/themes/bitwms-child/style.css | 105 +++++++++++++++ .../bitwms-child/templates/content-card.php | 78 +++++++++++ .../bitwms-child/templates/section-filter.php | 78 +++++++++++ 7 files changed, 484 insertions(+), 1 deletion(-) create mode 100644 wp-content/themes/bitwms-child/inc/ajax-filter.php create mode 100644 wp-content/themes/bitwms-child/js/ajax-filter.js create mode 100644 wp-content/themes/bitwms-child/templates/content-card.php create mode 100644 wp-content/themes/bitwms-child/templates/section-filter.php diff --git a/wp-content/themes/bitwms-child/functions.php b/wp-content/themes/bitwms-child/functions.php index 84a367d..df901f4 100644 --- a/wp-content/themes/bitwms-child/functions.php +++ b/wp-content/themes/bitwms-child/functions.php @@ -45,6 +45,7 @@ $bitwms_includes = array( 'inc/enqueue.php', 'inc/cpt.php', 'inc/acf-fields.php', + 'inc/ajax-filter.php', ); foreach ( $bitwms_includes as $bitwms_file ) { diff --git a/wp-content/themes/bitwms-child/inc/ajax-filter.php b/wp-content/themes/bitwms-child/inc/ajax-filter.php new file mode 100644 index 0000000..af1a160 --- /dev/null +++ b/wp-content/themes/bitwms-child/inc/ajax-filter.php @@ -0,0 +1,78 @@ + 'Security check failed.' ), 403 ); + } + + $valid_types = array( 'post', 'case', 'webinar', 'course', 'event', 'material' ); + $all_post_types = $valid_types; + + $raw_type = isset( $_POST['post_type'] ) ? sanitize_key( $_POST['post_type'] ) : 'all'; + $page = isset( $_POST['page'] ) ? max( 1, (int) $_POST['page'] ) : 1; + + if ( 'all' === $raw_type ) { + $post_type = $all_post_types; + } elseif ( in_array( $raw_type, $valid_types, true ) ) { + $post_type = array( $raw_type ); + } else { + $post_type = $all_post_types; + } + + $posts_per_page = 6; + + $query = new WP_Query( array( + 'post_type' => $post_type, + 'posts_per_page' => $posts_per_page, + 'paged' => $page, + 'orderby' => 'date', + 'order' => 'DESC', + 'post_status' => 'publish', + ) ); + + $html = ''; + $has_more = false; + + if ( $query->have_posts() ) { + ob_start(); + while ( $query->have_posts() ) { + $query->the_post(); + get_template_part( 'templates/content-card' ); + } + $html = ob_get_clean(); + wp_reset_postdata(); + + $has_more = ( $page * $posts_per_page ) < $query->found_posts; + } + + wp_send_json_success( array( + 'html' => $html, + 'has_more' => $has_more, + ) ); +} diff --git a/wp-content/themes/bitwms-child/inc/enqueue.php b/wp-content/themes/bitwms-child/inc/enqueue.php index 6488f09..632754f 100644 --- a/wp-content/themes/bitwms-child/inc/enqueue.php +++ b/wp-content/themes/bitwms-child/inc/enqueue.php @@ -16,13 +16,33 @@ add_action( 'wp_enqueue_scripts', 'bitwms_enqueue_scripts', 20 ); function bitwms_enqueue_scripts() { $theme_version = wp_get_theme()->get( 'Version' ); + $theme_uri = get_stylesheet_directory_uri(); // Sidebar navigation — loaded in footer (5th arg: true). wp_enqueue_script( 'bitwms-sidebar-nav', - get_stylesheet_directory_uri() . '/js/sidebar-nav.js', + $theme_uri . '/js/sidebar-nav.js', array(), // no dependencies $theme_version, true // load in footer ); + + // Content feed AJAX filter — front page only. + if ( is_front_page() ) { + wp_enqueue_script( + 'bitwms-ajax-filter', + $theme_uri . '/js/ajax-filter.js', + array(), + $theme_version, + true + ); + wp_localize_script( + 'bitwms-ajax-filter', + 'bitwmsAjax', + array( + 'url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'bitwms_filter_nonce' ), + ) + ); + } } diff --git a/wp-content/themes/bitwms-child/js/ajax-filter.js b/wp-content/themes/bitwms-child/js/ajax-filter.js new file mode 100644 index 0000000..d71a330 --- /dev/null +++ b/wp-content/themes/bitwms-child/js/ajax-filter.js @@ -0,0 +1,123 @@ +/** + * БИТ.WMS Child Theme — js/ajax-filter.js + * + * Handles filter button clicks and "Load more" for the content feed section. + * Communicates with the WordPress AJAX handler (bitwms_ajax_filter_handler). + * + * Global: bitwmsAjax { url: string, nonce: string } + */ + +( function () { + 'use strict'; + + document.addEventListener( 'DOMContentLoaded', function () { + var filterBar = document.querySelector( '.filter-bar' ); + var contentGrid = document.getElementById( 'content-grid' ); + var loadMoreBtn = document.getElementById( 'load-more-btn' ); + + if ( ! filterBar || ! contentGrid ) { + return; + } + + var currentFilter = 'all'; + var currentPage = 1; + var isLoading = false; + + // --------------------------------------------------------------- + // Filter button click + // --------------------------------------------------------------- + filterBar.addEventListener( 'click', function ( e ) { + var btn = e.target.closest( '.filter-bar__btn' ); + if ( ! btn ) return; + + var filter = btn.getAttribute( 'data-filter' ); + if ( filter === currentFilter ) return; + + // Update active state. + filterBar.querySelectorAll( '.filter-bar__btn' ).forEach( function ( b ) { + b.classList.remove( 'filter-bar__btn--active' ); + b.setAttribute( 'aria-selected', 'false' ); + } ); + btn.classList.add( 'filter-bar__btn--active' ); + btn.setAttribute( 'aria-selected', 'true' ); + + currentFilter = filter; + currentPage = 1; + + loadContent( false ); + } ); + + // --------------------------------------------------------------- + // Load more button click + // --------------------------------------------------------------- + if ( loadMoreBtn ) { + loadMoreBtn.addEventListener( 'click', function () { + if ( isLoading ) return; + currentPage += 1; + loadContent( true ); + } ); + } + + // --------------------------------------------------------------- + // loadContent( append ) + // append === false: replace grid content + // append === true: append to existing grid content + // --------------------------------------------------------------- + function loadContent( append ) { + if ( isLoading ) return; + isLoading = true; + + if ( loadMoreBtn ) { + loadMoreBtn.disabled = true; + loadMoreBtn.textContent = loadMoreBtn.getAttribute( 'data-loading' ) || 'Загрузка…'; + } + + var formData = new FormData(); + formData.append( 'action', 'bitwms_filter' ); + formData.append( 'nonce', bitwmsAjax.nonce ); + formData.append( 'post_type', currentFilter ); + formData.append( 'page', currentPage ); + + fetch( bitwmsAjax.url, { + method: 'POST', + body: formData, + } ) + .then( function ( response ) { + return response.json(); + } ) + .then( function ( data ) { + if ( ! data.success ) return; + + var html = data.data.html; + var hasMore = data.data.has_more; + + if ( append ) { + contentGrid.insertAdjacentHTML( 'beforeend', html ); + } else { + contentGrid.innerHTML = html || '

Материалы не найдены.

'; + } + + // Show/hide load-more button. + if ( loadMoreBtn ) { + if ( hasMore ) { + loadMoreBtn.style.display = ''; + loadMoreBtn.disabled = false; + loadMoreBtn.textContent = 'Загрузить ещё'; + } else { + loadMoreBtn.style.display = 'none'; + } + } + } ) + .catch( function ( err ) { + console.error( 'bitwms filter error:', err ); + if ( loadMoreBtn ) { + loadMoreBtn.disabled = false; + loadMoreBtn.textContent = 'Загрузить ещё'; + } + } ) + .finally( function () { + isLoading = false; + } ); + } + } ); +} )(); diff --git a/wp-content/themes/bitwms-child/style.css b/wp-content/themes/bitwms-child/style.css index 2e4e8dc..fa7d13b 100644 --- a/wp-content/themes/bitwms-child/style.css +++ b/wp-content/themes/bitwms-child/style.css @@ -720,3 +720,108 @@ body.sidebar-overlay::after { .clients-section { padding: 40px 0 48px; } .clients-section__grid { gap: 16px; } } + +/* ========================================================================== + Task 9: Content feed — filter bar + content grid (AJAX) + ========================================================================== */ + +/* --- Filter bar --- */ +.filter-bar { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 32px; + padding-bottom: 24px; + border-bottom: 1px solid var(--color-gray-light); +} + +.filter-bar__btn { + padding: 8px 20px; + font-size: 0.875rem; + background: none; + border: 1px solid var(--color-gray-light); + cursor: pointer; + color: var(--color-black); +} + +.filter-bar__btn:hover { + border-color: var(--color-black); +} + +.filter-bar__btn--active { + background: var(--color-black); + color: var(--color-white); + border-color: var(--color-black); + font-weight: 700; +} + +/* --- Content grid --- */ +.content-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + margin-bottom: 32px; +} + +.content-card__link { + display: block; + text-decoration: none; + color: inherit; +} + +.content-card__link:hover .content-card__title { + color: var(--color-primary); +} + +.content-card__image { + aspect-ratio: 16 / 10; + overflow: hidden; + margin-bottom: 16px; + background: var(--color-gray-light); +} + +.content-card__image img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.content-card__placeholder { + width: 100%; + height: 100%; +} + +.content-card__title { + font-size: 1.125rem; + margin: 0 0 8px; + line-height: 1.3; +} + +.content-card__excerpt { + font-size: 0.875rem; + color: var(--color-gray-text); + margin: 0 0 12px; +} + +.content-card__meta { + font-size: 0.75rem; + color: var(--color-gray-text); +} + +.content-feed__more { + text-align: center; + margin-bottom: 60px; +} + +/* --- Responsive breakpoints --- */ +@media (max-width: 1024px) { + .content-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 767px) { + .content-grid { + grid-template-columns: 1fr; + } +} diff --git a/wp-content/themes/bitwms-child/templates/content-card.php b/wp-content/themes/bitwms-child/templates/content-card.php new file mode 100644 index 0000000..d9c7336 --- /dev/null +++ b/wp-content/themes/bitwms-child/templates/content-card.php @@ -0,0 +1,78 @@ + __( 'Кейс', 'bitwms-child' ), + 'webinar' => __( 'Вебинар', 'bitwms-child' ), + 'course' => __( 'Курс', 'bitwms-child' ), + 'event' => __( 'Мероприятие', 'bitwms-child' ), + 'material' => __( 'Материал', 'bitwms-child' ), +); + +$post_type = get_post_type(); +$badge_text = ''; + +if ( 'post' === $post_type ) { + if ( has_category( 'news' ) ) { + $badge_text = __( 'Новость', 'bitwms-child' ); + } else { + $badge_text = __( 'Статья', 'bitwms-child' ); + } +} elseif ( isset( $type_labels[ $post_type ] ) ) { + $badge_text = $type_labels[ $post_type ]; +} + +// Trim excerpt to 15 words. +$excerpt = get_the_excerpt(); +if ( $excerpt ) { + $words = explode( ' ', $excerpt ); + $excerpt = implode( ' ', array_slice( $words, 0, 15 ) ); + if ( count( $words ) > 15 ) { + $excerpt .= '…'; + } +} +?> + +
+ + +
+ + esc_attr( get_the_title() ) ) ); ?> + + + +
+ +
+ + + + +

+ + +

+ + +
+ +
+
+ +
+
diff --git a/wp-content/themes/bitwms-child/templates/section-filter.php b/wp-content/themes/bitwms-child/templates/section-filter.php new file mode 100644 index 0000000..3f9aa15 --- /dev/null +++ b/wp-content/themes/bitwms-child/templates/section-filter.php @@ -0,0 +1,78 @@ + __( 'Все', 'bitwms-child' ), + 'post' => __( 'Статьи', 'bitwms-child' ), + 'case' => __( 'Кейсы', 'bitwms-child' ), + 'webinar' => __( 'Вебинары', 'bitwms-child' ), + 'course' => __( 'Курсы', 'bitwms-child' ), + 'event' => __( 'Мероприятия', 'bitwms-child' ), + 'material'=> __( 'Материалы', 'bitwms-child' ), +); + +$post_types = array( 'post', 'case', 'webinar', 'course', 'event', 'material' ); +$posts_per_page = 6; + +$initial_query = new WP_Query( array( + 'post_type' => $post_types, + 'posts_per_page' => $posts_per_page, + 'orderby' => 'date', + 'order' => 'DESC', + 'post_status' => 'publish', +) ); +?> + +
+
+ +
+ $label ) : ?> + + +
+ +
+ have_posts() ) : ?> + have_posts() ) : $initial_query->the_post(); ?> + + + + +

+ +
+ + found_posts > $posts_per_page ) : ?> +
+ +
+ + +
+