Files

254 lines
6.4 KiB
Plaintext

<div class="subheader-search-container">
<form
class="lang-en"
id="client-search-form"
onsubmit="return false;"
data-test="desktop-search">
<i class="subheader-search-icon far fa-search"></i>
<input
aria-label="Search"
class="subheader-search-input"
type="text"
id="client-search-input"
placeholder="Browse by titles or tags..."
autocomplete="off" />
</form>
<!-- No Results Message -->
<div id="no-results-message" class="no-results-message" style="display: none;">
<p>No matches found. Try different keywords or <a href="/courses">browse all lessons</a>.</p>
</div>
</div>
<script>
(function() {
'use strict';
const searchInput = document.getElementById('client-search-input');
const noResultsMessage = document.getElementById('no-results-message');
if (!searchInput) return;
// Get all searchable cards
function getSearchableCards() {
return document.querySelectorAll('.np-card');
}
// Extract searchable text from a card
function getCardSearchText(card) {
const texts = [];
// Get course/learning path title
const title = card.querySelector('.np-card-content-title');
if (title) texts.push(title.textContent.trim());
// Get instructor names
const instructor = card.querySelector('.np-card-content-subtitle');
if (instructor) texts.push(instructor.textContent.trim());
// Get description (learning paths have this)
const description = card.querySelector('.np-card-content-description');
if (description) texts.push(description.textContent.trim());
// Get ribbon/tag content
const ribbon = card.querySelector('.np-card-ribbon');
if (ribbon) texts.push(ribbon.textContent.trim());
// Also get any badge/type content that might contain searchable terms
const badges = card.querySelectorAll('.np-card-header-type, .np-product-badge-name');
badges.forEach(badge => {
if (badge) texts.push(badge.textContent.trim());
});
// Normalize the text - remove extra whitespace, normalize characters
const fullText = texts.join(' ').toLowerCase()
.replace(/\s+/g, ' ') // Replace multiple spaces with single space
.replace(/[^\w\s]/g, ' ') // Replace non-word chars (except spaces) with spaces
.trim();
return fullText;
}
// Get the grid container for a card (Bootstrap column)
function getCardContainer(card) {
// Look for the Bootstrap grid column container (col-*)
let container = card.parentElement;
while (container && container !== document.body) {
if (container.classList && Array.from(container.classList).some(cls => cls.startsWith('col-'))) {
return container;
}
container = container.parentElement;
}
// Fallback to the card itself if no grid container found
return card;
}
// Perform the search
function performSearch(query) {
const cards = getSearchableCards();
const searchTerms = query.toLowerCase().trim().split(/\s+/).filter(term => term.length > 0);
if (searchTerms.length === 0) {
// Show all cards
cards.forEach(card => {
const container = getCardContainer(card);
container.style.display = '';
});
noResultsMessage.style.display = 'none';
return;
}
let visibleCount = 0;
cards.forEach(card => {
const cardText = getCardSearchText(card);
const matches = searchTerms.every(term => cardText.includes(term));
const container = getCardContainer(card);
if (matches) {
container.style.display = '';
visibleCount++;
} else {
container.style.display = 'none';
}
});
// Show/hide no results message
noResultsMessage.style.display = visibleCount === 0 ? 'block' : 'none';
}
// Debounced search
let searchTimeout;
function debouncedSearch(query) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => performSearch(query), 150);
}
// Event listeners
searchInput.addEventListener('input', function(e) {
debouncedSearch(e.target.value);
});
// Clear search on escape
searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
e.target.value = '';
performSearch('');
}
});
})();
</script>
<style>
.subheader-search-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
padding: 10px 16px 30px 16px;
position: relative;
}
.subheader-search-container > form {
width: 60%;
max-width: 600px;
min-width: 300px;
position: relative;
margin-bottom: 8px;
}
/* Mobile Search Bar */
@media (max-width: 768px) {
.subheader-search-container > form {
width: 100%;
min-width: unset;
}
}
.subheader-search-icon {
position: absolute;
font-size: 20px;
font-weight: bold;
top: 12px;
left: 18px;
color: #888;
transition: color 0.2s ease;
}
.subheader-search-input {
width: 100%;
padding: 12px 12px 12px 50px;
border-radius: 8px;
border: 2px solid #e0e0e0;
font-size: 16px;
background-color: white;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.subheader-search-input:focus {
outline: none;
border-color: #4CAF50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
}
.subheader-search-input:focus + .subheader-search-icon,
.subheader-search-input:not(:placeholder-shown) + .subheader-search-icon {
color: #4CAF50;
}
/* Search Tips */
.search-tips {
width: 60%;
max-width: 600px;
min-width: 300px;
text-align: center;
}
.search-tip {
color: #666;
font-size: 13px;
font-weight: 400;
line-height: 1.3;
}
/* No Results Message */
.no-results-message {
width: 60%;
max-width: 600px;
min-width: 300px;
text-align: center;
margin-top: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
.no-results-message p {
color: #666;
margin: 0;
font-size: 14px;
}
.no-results-message a {
color: #013d29;
text-decoration: underline;
}
.no-results-message a:hover {
color: #022f20;
}
@media (max-width: 768px) {
.search-tips, .no-results-message {
width: 100%;
min-width: unset;
padding: 0 10px;
}
.search-tip {
font-size: 12px;
}
}
</style>