feat: implement premium blog search and filter UI with improved layout and dynamic post loading.
This commit is contained in:
@@ -42,6 +42,16 @@ class BlogController extends Controller
|
||||
$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
|
||||
? $query->latest('published_at')->paginate(12)
|
||||
: collect();
|
||||
|
||||
@@ -131,6 +131,7 @@ var theme = {
|
||||
if(grids != null) {
|
||||
grids.forEach(g => {
|
||||
var itemgrid = g.querySelector('.isotope');
|
||||
if (itemgrid == null) return;
|
||||
var filtersElem = g.querySelector('.isotope-filter');
|
||||
var buttonGroups = g.querySelectorAll('.isotope-filter');
|
||||
var iso = new Isotope(itemgrid, {
|
||||
@@ -216,9 +217,11 @@ var theme = {
|
||||
if (top >= offset && top < offset + height) {
|
||||
navLinks.forEach(links => {
|
||||
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();
|
||||
this.blur();
|
||||
const href = this.getAttribute("href");
|
||||
const offsetTop = document.querySelector(href).offsetTop;
|
||||
scroll({
|
||||
top: offsetTop,
|
||||
behavior: "smooth"
|
||||
});
|
||||
const target = document.querySelector(href);
|
||||
if (target) {
|
||||
const offsetTop = target.offsetTop;
|
||||
scroll({
|
||||
top: offsetTop,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<!-- Carousel Section -->
|
||||
@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-wrapper">
|
||||
@foreach($carouselPosts as $cPost)
|
||||
@@ -21,9 +21,9 @@
|
||||
$cUrl = route('blog.show', $cPost->slug);
|
||||
@endphp
|
||||
<div class="swiper-slide">
|
||||
<figure class="overlay caption caption-overlay rounded !mb-0">
|
||||
<a href="{{ $cUrl }}">
|
||||
<img src="{{ $cImage }}" alt="{{ $cTitle }}" style="height: 380px; object-fit: cover; width: 100%;" loading="lazy">
|
||||
<figure class="overlay caption caption-overlay rounded !mb-0" style="height: 380px !important; overflow: hidden;">
|
||||
<a href="{{ $cUrl }}" class="block w-full h-full">
|
||||
<img src="{{ $cImage }}" alt="{{ $cTitle }}" style="height: 380px !important; object-fit: cover !important; width: 100% !important;" loading="lazy">
|
||||
</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">
|
||||
@if($cCategoryName)
|
||||
@@ -58,7 +58,7 @@
|
||||
<!-- Welcome Banner Section -->
|
||||
<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">
|
||||
<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.
|
||||
</h2>
|
||||
</div>
|
||||
@@ -71,7 +71,7 @@
|
||||
// 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';
|
||||
@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)
|
||||
@php
|
||||
$images = [
|
||||
@@ -104,26 +104,40 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Main Content & Sidebar Layout -->
|
||||
<div class="flex flex-wrap mx-[-15px] xl:mx-[-35px] lg:mx-[-20px] !mt-[4rem]">
|
||||
|
||||
<!-- 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 -->
|
||||
<!-- Category Filter & Search Bar Wrapper -->
|
||||
<div class="flex flex-wrap justify-between items-center gap-4 !mb-8 blog-filter-search-wrapper">
|
||||
@if(isset($categories) && $categories->count() > 0)
|
||||
<div class="flex flex-wrap justify-start !mb-10 gap-2 blog-filter-menu">
|
||||
<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="">
|
||||
<div class="blog-filter-menu !mb-0">
|
||||
<a href="#" class="category-filter-btn active" data-category="">
|
||||
Tümü
|
||||
</a>
|
||||
@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 }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@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]">
|
||||
@include('blog.partials.posts')
|
||||
</div>
|
||||
@@ -219,6 +233,131 @@
|
||||
|
||||
@push('styles')
|
||||
<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 */
|
||||
.blog-loading-overlay {
|
||||
position: absolute;
|
||||
@@ -285,7 +424,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
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(() => {
|
||||
overlay.classList.remove('active');
|
||||
@@ -314,11 +456,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
e.preventDefault();
|
||||
|
||||
filterBtns.forEach(b => {
|
||||
b.classList.remove('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
|
||||
b.classList.add('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
|
||||
b.classList.remove('active');
|
||||
});
|
||||
this.classList.remove('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
|
||||
this.classList.add('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
|
||||
this.classList.add('active');
|
||||
|
||||
const category = this.getAttribute('data-category');
|
||||
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) {
|
||||
if (e.state && e.state.html) {
|
||||
container.innerHTML = e.state.html;
|
||||
@@ -341,15 +520,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const currentCategory = urlParams.get('category') || '';
|
||||
const currentSearch = urlParams.get('search') || '';
|
||||
|
||||
filterBtns.forEach(b => {
|
||||
b.classList.remove('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
|
||||
b.classList.add('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
|
||||
b.classList.remove('active');
|
||||
if (b.getAttribute('data-category') === currentCategory) {
|
||||
b.classList.remove('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
|
||||
b.classList.add('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
|
||||
b.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.value = currentSearch;
|
||||
}
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
@@ -363,11 +545,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const activeBtn = Array.from(filterBtns).find(btn => btn.getAttribute('data-category') === initialCategory);
|
||||
if (activeBtn) {
|
||||
filterBtns.forEach(b => {
|
||||
b.classList.remove('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
|
||||
b.classList.add('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
|
||||
b.classList.remove('active');
|
||||
});
|
||||
activeBtn.classList.remove('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
|
||||
activeBtn.classList.add('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
|
||||
activeBtn.classList.add('active');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user