feat: implement premium blog search and filter UI with improved layout and dynamic post loading.

This commit is contained in:
Ümit Tunç
2026-05-25 06:01:35 +03:00
parent a037917f16
commit afa92d8f4a
3 changed files with 232 additions and 36 deletions
+10
View File
@@ -42,6 +42,16 @@ class BlogController extends Controller
$query = $query->whereJsonContains('tags', $request->tag); $query = $query->whereJsonContains('tags', $request->tag);
} }
// Search filtresi
if ($query && $request->has('search') && $request->search) {
$search = $request->search;
$query = $query->where(function($q) use ($search) {
$q->where('title', 'like', '%' . $search . '%')
->orWhere('content', 'like', '%' . $search . '%')
->orWhere('excerpt', 'like', '%' . $search . '%');
});
}
$posts = $query $posts = $query
? $query->latest('published_at')->paginate(12) ? $query->latest('published_at')->paginate(12)
: collect(); : collect();
+13 -7
View File
@@ -131,6 +131,7 @@ var theme = {
if(grids != null) { if(grids != null) {
grids.forEach(g => { grids.forEach(g => {
var itemgrid = g.querySelector('.isotope'); var itemgrid = g.querySelector('.isotope');
if (itemgrid == null) return;
var filtersElem = g.querySelector('.isotope-filter'); var filtersElem = g.querySelector('.isotope-filter');
var buttonGroups = g.querySelectorAll('.isotope-filter'); var buttonGroups = g.querySelectorAll('.isotope-filter');
var iso = new Isotope(itemgrid, { var iso = new Isotope(itemgrid, {
@@ -216,9 +217,11 @@ var theme = {
if (top >= offset && top < offset + height) { if (top >= offset && top < offset + height) {
navLinks.forEach(links => { navLinks.forEach(links => {
links.classList.remove('active'); links.classList.remove('active');
document.querySelector(`.nav-link.scroll[href*=${id}]`).classList.add('active');
//[att*=val] Represents an element with the att attribute whose value contains at least one instance of the substring "val". If "val" is the empty string then the selector does not represent anything.
}); });
let activeLink = document.querySelector(`.nav-link.scroll[href*="${id}"]`);
if (activeLink) {
activeLink.classList.add('active');
}
} }
}); });
} }
@@ -237,11 +240,14 @@ var theme = {
e.preventDefault(); e.preventDefault();
this.blur(); this.blur();
const href = this.getAttribute("href"); const href = this.getAttribute("href");
const offsetTop = document.querySelector(href).offsetTop; const target = document.querySelector(href);
scroll({ if (target) {
top: offsetTop, const offsetTop = target.offsetTop;
behavior: "smooth" scroll({
}); top: offsetTop,
behavior: "smooth"
});
}
} }
}, },
/** /**
+209 -29
View File
@@ -7,7 +7,7 @@
<!-- Carousel Section --> <!-- Carousel Section -->
@if(isset($carouselPosts) && $carouselPosts->count() > 0) @if(isset($carouselPosts) && $carouselPosts->count() > 0)
<div class="swiper-container blog grid-view !mb-24 relative z-10" data-margin="30" data-dots="true" data-items-lg="2" data-items-md="1" data-items-xs="1"> <div class="swiper-container blog grid-view !mb-10 relative z-10" data-margin="30" data-dots="true" data-items-lg="2" data-items-md="1" data-items-xs="1">
<div class="swiper"> <div class="swiper">
<div class="swiper-wrapper"> <div class="swiper-wrapper">
@foreach($carouselPosts as $cPost) @foreach($carouselPosts as $cPost)
@@ -21,9 +21,9 @@
$cUrl = route('blog.show', $cPost->slug); $cUrl = route('blog.show', $cPost->slug);
@endphp @endphp
<div class="swiper-slide"> <div class="swiper-slide">
<figure class="overlay caption caption-overlay rounded !mb-0"> <figure class="overlay caption caption-overlay rounded !mb-0" style="height: 380px !important; overflow: hidden;">
<a href="{{ $cUrl }}"> <a href="{{ $cUrl }}" class="block w-full h-full">
<img src="{{ $cImage }}" alt="{{ $cTitle }}" style="height: 380px; object-fit: cover; width: 100%;" loading="lazy"> <img src="{{ $cImage }}" alt="{{ $cTitle }}" style="height: 380px !important; object-fit: cover !important; width: 100% !important;" loading="lazy">
</a> </a>
<figcaption class="group-hover:opacity-100 absolute w-full h-full opacity-0 text-center px-4 py-3 inset-0 z-[5] pointer-events-none p-2"> <figcaption class="group-hover:opacity-100 absolute w-full h-full opacity-0 text-center px-4 py-3 inset-0 z-[5] pointer-events-none p-2">
@if($cCategoryName) @if($cCategoryName)
@@ -58,7 +58,7 @@
<!-- Welcome Banner Section --> <!-- Welcome Banner Section -->
<div class="flex flex-wrap mx-[-15px]"> <div class="flex flex-wrap mx-[-15px]">
<div class="lg:w-full xl:w-10/12 xxl:w-8/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto !text-center"> <div class="lg:w-full xl:w-10/12 xxl:w-8/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto !text-center">
<h2 class="xl:!text-[1.7rem] !text-[calc(1.295rem_+_0.54vw)] !leading-[1.25] font-semibold !text-center !mt-4 !mb-10"> <h2 class="xl:!text-[1.7rem] !text-[calc(1.295rem_+_0.54vw)] !leading-[1.25] font-semibold !text-center !mt-0 !mb-4">
Merhaba! Biz Trunçgil. Blogumuza hoş geldiniz. Burada web tasarım, yazılım geliştirme ve teknolojik yeniliklere dair ipuçlarını ve deneyimlerimizi paylaşıyoruz. Merhaba! Biz Trunçgil. Blogumuza hoş geldiniz. Burada web tasarım, yazılım geliştirme ve teknolojik yeniliklere dair ipuçlarını ve deneyimlerimizi paylaşıyoruz.
</h2> </h2>
</div> </div>
@@ -71,7 +71,7 @@
// Dynamically select column width based on category count // Dynamically select column width based on category count
$gridClass = $catCount >= 4 ? 'md:w-6/12 lg:w-3/12 xl:w-3/12' : 'md:w-6/12 lg:w-4/12 xl:w-4/12'; $gridClass = $catCount >= 4 ? 'md:w-6/12 lg:w-3/12 xl:w-3/12' : 'md:w-6/12 lg:w-4/12 xl:w-4/12';
@endphp @endphp
<div class="flex flex-wrap mx-[-15px] grid-view md:mx-[-20px] lg:mx-[-20px] xl:mx-[-25px] !mt-[-40px] xl:!mt-0 lg:!mt-0 !text-center !mb-16"> <div class="flex flex-wrap mx-[-15px] grid-view md:mx-[-20px] lg:mx-[-20px] xl:mx-[-25px] !mt-[-40px] xl:!mt-0 lg:!mt-0 !text-center !mb-8">
@foreach($highlightCategories as $index => $hCat) @foreach($highlightCategories as $index => $hCat)
@php @php
$images = [ $images = [
@@ -104,26 +104,40 @@
</div> </div>
@endif @endif
<!-- Main Content & Sidebar Layout --> <!-- Category Filter & Search Bar Wrapper -->
<div class="flex flex-wrap mx-[-15px] xl:mx-[-35px] lg:mx-[-20px] !mt-[4rem]"> <div class="flex flex-wrap justify-between items-center gap-4 !mb-8 blog-filter-search-wrapper">
<!-- Left Column: Posts Feed (8/12) -->
<div class="xl:w-8/12 lg:w-8/12 w-full flex-[0_0_auto] !px-[15px] max-w-full lg:!px-[20px] xl:!px-[35px] xl:!mt-0 lg:!mt-0">
<!-- Category Filter -->
@if(isset($categories) && $categories->count() > 0) @if(isset($categories) && $categories->count() > 0)
<div class="flex flex-wrap justify-start !mb-10 gap-2 blog-filter-menu"> <div class="blog-filter-menu !mb-0">
<a href="#" class="category-filter-btn active !text-[0.75rem] !font-bold !uppercase !tracking-[0.02rem] !px-4 !py-2 !rounded-full !border !border-[#e31e24] !bg-[#e31e24] !text-white hover:!bg-[#c8191f] hover:!border-[#c8191f] transition-colors" data-category=""> <a href="#" class="category-filter-btn active" data-category="">
Tümü Tümü
</a> </a>
@foreach($categories as $category) @foreach($categories as $category)
<a href="#" class="category-filter-btn !text-[0.75rem] !font-bold !uppercase !tracking-[0.02rem] !px-4 !py-2 !rounded-full !border !border-[#eef2f6] !bg-[#eef2f6] !text-[#343f52] hover:!bg-[#e31e24] hover:!text-white hover:!border-[#e31e24] transition-colors" data-category="{{ $category->slug }}"> <a href="#" class="category-filter-btn" data-category="{{ $category->slug }}">
{{ $category->name }} {{ $category->name }}
</a> </a>
@endforeach @endforeach
</div> </div>
@endif @endif
<!-- Premium Search Box -->
<div class="blog-search-box">
<form action="#" class="blog-search-form" id="blog-search-form">
<input type="text" name="search" id="blog-search-input" placeholder="Yazılarda ara..." autocomplete="off">
<button type="submit" class="blog-search-submit-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16" style="width: 14px; height: 14px; display: block; color: white;">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>
</svg>
</button>
</form>
</div>
</div>
<!-- Main Content & Sidebar Layout -->
<div class="flex flex-wrap mx-[-15px] xl:mx-[-35px] lg:mx-[-20px] !mt-[1.5rem]">
<!-- Left Column: Posts Feed (8/12) -->
<div class="xl:w-8/12 lg:w-8/12 w-full flex-[0_0_auto] !px-[15px] max-w-full lg:!px-[20px] xl:!px-[35px] xl:!mt-0 lg:!mt-0">
<div id="blog-posts-container" class="relative min-h-[400px]"> <div id="blog-posts-container" class="relative min-h-[400px]">
@include('blog.partials.posts') @include('blog.partials.posts')
</div> </div>
@@ -219,6 +233,131 @@
@push('styles') @push('styles')
<style> <style>
/* Premium Blog Filter Menu */
.blog-filter-menu {
display: inline-flex !important;
flex-wrap: wrap !important;
gap: 8px !important;
background: #ffffff !important;
padding: 8px 12px !important;
border-radius: 50px !important;
border: 1px solid #eef2f6 !important;
box-shadow: 0 4px 20px rgba(30, 34, 40, 0.05) !important;
margin-bottom: 0 !important;
align-items: center !important;
}
.category-filter-btn {
font-size: 0.75rem !important;
font-weight: 700 !important;
padding: 8px 20px !important;
border-radius: 50px !important;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
border: 1px solid transparent !important;
color: #60697b !important;
background: transparent !important;
text-transform: uppercase !important;
letter-spacing: 0.03em !important;
text-decoration: none !important;
cursor: pointer !important;
}
.category-filter-btn:hover {
color: #e31e24 !important;
background: rgba(227, 30, 36, 0.05) !important;
text-decoration: none !important;
}
.category-filter-btn.active {
color: #ffffff !important;
background: #e31e24 !important;
border-color: #e31e24 !important;
box-shadow: 0 4px 12px rgba(227, 30, 36, 0.25) !important;
text-decoration: none !important;
}
/* Premium Blog Search Box */
.blog-search-box {
background: #ffffff !important;
border-radius: 50px !important;
border: 1px solid #eef2f6 !important;
box-shadow: 0 4px 20px rgba(30, 34, 40, 0.05) !important;
padding: 4px 6px 4px 18px !important;
display: inline-flex !important;
align-items: center !important;
width: 280px !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
}
.blog-search-box:focus-within {
border-color: #e31e24 !important;
box-shadow: 0 4px 20px rgba(227, 30, 36, 0.1) !important;
width: 320px !important;
}
.blog-search-form {
display: flex !important;
width: 100% !important;
align-items: center !important;
margin: 0 !important;
}
#blog-search-input {
border: none !important;
background: transparent !important;
outline: none !important;
font-size: 0.8rem !important;
font-weight: 500 !important;
color: #343f52 !important;
width: 100% !important;
padding: 6px 0 !important;
}
#blog-search-input::placeholder {
color: #aab0bc !important;
}
.blog-search-submit-btn {
border: none !important;
background: #e31e24 !important;
color: #ffffff !important;
width: 32px !important;
height: 32px !important;
border-radius: 50% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
transition: all 0.25s ease !important;
box-shadow: 0 4px 10px rgba(227, 30, 36, 0.2) !important;
flex-shrink: 0 !important;
padding: 0 !important;
margin: 0 !important;
}
.blog-search-submit-btn:hover {
background: #c8191f !important;
transform: scale(1.05) !important;
}
@media (max-width: 991px) {
.blog-filter-search-wrapper {
flex-direction: column !important;
align-items: stretch !important;
gap: 15px !important;
}
.blog-filter-menu {
justify-content: center !important;
width: 100% !important;
}
.blog-search-box {
width: 100% !important;
}
}
/* Loading Overlay for AJAX Requests */ /* Loading Overlay for AJAX Requests */
.blog-loading-overlay { .blog-loading-overlay {
position: absolute; position: absolute;
@@ -285,7 +424,10 @@ document.addEventListener('DOMContentLoaded', function() {
window.history.pushState({html: html}, '', url); window.history.pushState({html: html}, '', url);
document.querySelector('.blog-filter-menu').scrollIntoView({ behavior: 'smooth', block: 'start' }); const scrollTarget = document.querySelector('.blog-filter-search-wrapper') || document.querySelector('.blog-filter-menu');
if (scrollTarget) {
scrollTarget.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
setTimeout(() => { setTimeout(() => {
overlay.classList.remove('active'); overlay.classList.remove('active');
@@ -314,11 +456,9 @@ document.addEventListener('DOMContentLoaded', function() {
e.preventDefault(); e.preventDefault();
filterBtns.forEach(b => { filterBtns.forEach(b => {
b.classList.remove('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]'); b.classList.remove('active');
b.classList.add('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
}); });
this.classList.remove('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]'); this.classList.add('active');
this.classList.add('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
const category = this.getAttribute('data-category'); const category = this.getAttribute('data-category');
const url = new URL(window.location.href); const url = new URL(window.location.href);
@@ -333,6 +473,45 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
}); });
// Bind Search Form
const searchForm = document.getElementById('blog-search-form');
const searchInput = document.getElementById('blog-search-input');
if (searchForm && searchInput) {
searchForm.addEventListener('submit', function(e) {
e.preventDefault();
const query = searchInput.value.trim();
const url = new URL(window.location.href);
if (query) {
url.searchParams.set('search', query);
} else {
url.searchParams.delete('search');
}
url.searchParams.delete('page');
fetchPosts(url.toString());
});
// Clear search automatically when input is empty
searchInput.addEventListener('input', function() {
if (this.value.trim() === '') {
const url = new URL(window.location.href);
if (url.searchParams.has('search')) {
url.searchParams.delete('search');
url.searchParams.delete('page');
fetchPosts(url.toString());
}
}
});
// Prepopulate search parameter from URL
const urlParams = new URLSearchParams(window.location.search);
const initialSearch = urlParams.get('search') || '';
if (initialSearch) {
searchInput.value = initialSearch;
}
}
window.addEventListener('popstate', function(e) { window.addEventListener('popstate', function(e) {
if (e.state && e.state.html) { if (e.state && e.state.html) {
container.innerHTML = e.state.html; container.innerHTML = e.state.html;
@@ -341,15 +520,18 @@ document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const currentCategory = urlParams.get('category') || ''; const currentCategory = urlParams.get('category') || '';
const currentSearch = urlParams.get('search') || '';
filterBtns.forEach(b => { filterBtns.forEach(b => {
b.classList.remove('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]'); b.classList.remove('active');
b.classList.add('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
if (b.getAttribute('data-category') === currentCategory) { if (b.getAttribute('data-category') === currentCategory) {
b.classList.remove('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]'); b.classList.add('active');
b.classList.add('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
} }
}); });
if (searchInput) {
searchInput.value = currentSearch;
}
} else { } else {
window.location.reload(); window.location.reload();
} }
@@ -363,11 +545,9 @@ document.addEventListener('DOMContentLoaded', function() {
const activeBtn = Array.from(filterBtns).find(btn => btn.getAttribute('data-category') === initialCategory); const activeBtn = Array.from(filterBtns).find(btn => btn.getAttribute('data-category') === initialCategory);
if (activeBtn) { if (activeBtn) {
filterBtns.forEach(b => { filterBtns.forEach(b => {
b.classList.remove('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]'); b.classList.remove('active');
b.classList.add('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
}); });
activeBtn.classList.remove('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]'); activeBtn.classList.add('active');
activeBtn.classList.add('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
} }
} }
}); });