519 lines
18 KiB
Plaintext
Raw Normal View History

2025-05-03 01:35:48 -07:00
---
/**
2025-05-03 18:22:25 -07:00
* SearchScript component
* Imports the modular search functionality
2025-05-03 01:35:48 -07:00
*/
2025-05-03 18:22:25 -07:00
---
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
<!-- Register Alpine.js components -->
<script is:inline>
document.addEventListener('alpine:init', () => {
if (typeof Alpine !== 'undefined') {
// Register the styleControls component
Alpine.data('styleControls', () => ({
iconSize: 'medium',
viewMode: 'grid',
displayMode: 'both',
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
init() {
// Load preferences from localStorage
this.loadPreferences();
},
loadPreferences() {
if (typeof localStorage !== 'undefined') {
try {
// Load icon size
const savedIconSize = localStorage.getItem('services-icon-size');
if (savedIconSize) {
this.setIconSize(savedIconSize);
}
// Load view mode
const savedViewMode = localStorage.getItem('services-view-mode');
if (savedViewMode) {
this.viewMode = savedViewMode;
}
// Load display mode
const savedDisplayMode = localStorage.getItem('services-display-mode');
if (savedDisplayMode) {
this.displayMode = savedDisplayMode;
}
} catch (e) {
console.error('Error loading preferences:', e);
}
}
},
savePreferences() {
if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem('services-icon-size', this.iconSize);
localStorage.setItem('services-view-mode', this.viewMode);
localStorage.setItem('services-display-mode', this.displayMode);
} catch (e) {
console.error('Error saving preferences:', e);
}
}
},
setIconSize(size) {
this.iconSize = size;
this.savePreferences();
},
toggleViewMode() {
this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid';
this.savePreferences();
},
setDisplayMode(mode) {
this.displayMode = mode;
this.savePreferences();
2025-05-03 01:35:48 -07:00
}
2025-05-03 18:22:25 -07:00
}));
// Register searchServices component
Alpine.data('searchServices', () => ({
searchQuery: '',
hasResults: true,
visibleCount: 0,
loading: false,
focusedItemIndex: -1,
debounceTimeout: null,
// View mode properties
iconSizeValue: 2, // Slider value: 1=small, 2=medium, 3=large
iconSize: 'medium', // small, medium, large
viewMode: 'grid', // grid or list
displayMode: 'both', // both, image, or name
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
init() {
// Initialize the visible count
this.visibleCount = document.querySelectorAll('.app-card').length;
this.setupWatchers();
this.setupKeyboardShortcuts();
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
// Apply initial icon size, view mode, and display mode
this.applyIconSize();
this.applyViewMode();
this.applyDisplayMode();
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
// Save preferences to localStorage if available
this.loadPreferences();
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
// Listen for window resize events to optimize layout
this.setupResizeListener();
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
// Set loading to false after initialization
setTimeout(() => {
this.loading = false;
}, 300);
},
setupWatchers() {
this.$watch('searchQuery', (query) => {
// Debounce search for better performance
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout);
}
this.debounceTimeout = setTimeout(() => {
this.filterContent(query);
}, 150);
});
},
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// '/' key focuses the search input
if (e.key === '/' && document.activeElement.id !== 'app-search') {
e.preventDefault();
document.getElementById('app-search').focus();
}
// Escape key clears the search
if (e.key === 'Escape' && this.searchQuery !== '') {
this.searchQuery = '';
document.getElementById('app-search').focus();
this.focusedItemIndex = -1;
this.clearItemFocus();
}
});
},
// Load user preferences from localStorage
loadPreferences() {
if (typeof localStorage !== 'undefined') {
try {
// Load icon size
const savedIconSize = localStorage.getItem('services-icon-size');
if (savedIconSize) {
this.setIconSize(savedIconSize);
}
// Load view mode
const savedViewMode = localStorage.getItem('services-view-mode');
if (savedViewMode) {
this.setViewMode(savedViewMode);
}
// Load display mode
const savedDisplayMode = localStorage.getItem('services-display-mode');
if (savedDisplayMode) {
this.setDisplayMode(savedDisplayMode);
}
} catch (e) {
console.error('Error loading preferences:', e);
}
2025-05-03 01:35:48 -07:00
}
2025-05-03 18:22:25 -07:00
},
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
// Save user preferences to localStorage
savePreferences() {
if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem('services-icon-size', this.iconSize);
localStorage.setItem('services-view-mode', this.viewMode);
localStorage.setItem('services-display-mode', this.displayMode);
} catch (e) {
console.error('Error saving preferences:', e);
}
2025-05-03 01:35:48 -07:00
}
2025-05-03 18:22:25 -07:00
},
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
// Setup listener for window resize events
setupResizeListener() {
const handleResize = () => {
// Switch to list view on small screens if not explicitly set by user
const userHasSetViewMode = localStorage.getItem('services-view-mode') !== null;
if (!userHasSetViewMode) {
const smallScreen = window.innerWidth < 640; // sm breakpoint
if (smallScreen && this.viewMode !== 'list') {
this.setViewMode('list', false); // Don't save to preferences
} else if (!smallScreen && this.viewMode !== 'grid') {
this.setViewMode('grid', false); // Don't save to preferences
}
}
};
// Initial check
handleResize();
// Add resize listener with debounce
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(handleResize, 250);
});
},
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
// Icon size methods
setIconSize(size) {
this.iconSize = size;
this.applyIconSize();
this.savePreferences();
},
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
applyIconSize() {
const appList = document.getElementById('app-list');
if (!appList) return;
// Remove existing size classes
appList.classList.remove('icon-size-small', 'icon-size-medium', 'icon-size-large');
// Add the new size class
appList.classList.add(`icon-size-${this.iconSize}`);
},
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
// View mode methods
toggleViewMode() {
this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid';
this.applyViewMode();
this.savePreferences();
},
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
setViewMode(mode, savePreference = true) {
this.viewMode = mode;
this.applyViewMode();
2025-05-03 01:35:48 -07:00
2025-05-03 18:22:25 -07:00
// Save preference if requested
if (savePreference) {
this.savePreferences();
2025-05-03 01:35:48 -07:00
}
2025-05-03 18:22:25 -07:00
},
2025-05-03 18:22:25 -07:00
applyViewMode() {
const appList = document.getElementById('app-list');
if (!appList) return;
// Remove existing view mode classes
appList.classList.remove('view-mode-grid', 'view-mode-list');
// Add the new view mode class
appList.classList.add(`view-mode-${this.viewMode}`);
// Update all category sections
document.querySelectorAll('.category-section').forEach(section => {
const gridContainer = section.querySelector('.grid');
if (gridContainer) {
// Update grid classes based on view mode
if (this.viewMode === 'grid') {
gridContainer.classList.remove('grid-cols-1');
gridContainer.classList.add('grid-cols-2', 'sm:grid-cols-3', 'lg:grid-cols-4');
} else {
gridContainer.classList.remove('grid-cols-2', 'sm:grid-cols-3', 'lg:grid-cols-4');
gridContainer.classList.add('grid-cols-1');
}
}
});
},
// Display mode methods
setDisplayMode(mode) {
this.displayMode = mode;
this.applyDisplayMode();
this.savePreferences();
},
applyDisplayMode() {
const appList = document.getElementById('app-list');
if (!appList) return;
// Remove existing display mode classes
appList.classList.remove('display-both', 'display-image-only', 'display-name-only');
2025-05-03 18:22:25 -07:00
// Add the new display mode class
if (this.displayMode === 'image') {
appList.classList.add('display-image-only');
} else if (this.displayMode === 'name') {
appList.classList.add('display-name-only');
} else {
2025-05-03 18:22:25 -07:00
appList.classList.add('display-both');
}
2025-05-03 18:22:25 -07:00
},
2025-05-03 18:22:25 -07:00
filterContent(query) {
query = query.toLowerCase().trim();
let anyResults = false;
let visibleCount = 0;
// Process all content items
document.querySelectorAll('.app-card').forEach((item) => {
const name = (item.getAttribute('data-app-name') || '').toLowerCase();
const tags = (item.getAttribute('data-app-tags') || '').toLowerCase();
const category = (item.getAttribute('data-app-category') || '').toLowerCase();
const isMatch = query === '' ||
name.includes(query) ||
tags.includes(query) ||
category.includes(query);
if (isMatch) {
item.style.display = '';
anyResults = true;
visibleCount++;
} else {
item.style.display = 'none';
}
});
// Update category visibility
document.querySelectorAll('.category-section').forEach((categorySection) => {
const items = categorySection.querySelectorAll('.app-card');
// Count visible items in this category
const visibleItems = Array.from(items).filter(item =>
item.style.display !== 'none'
).length;
// If no visible items and we're searching, hide the category
if (query !== '' && visibleItems === 0) {
categorySection.style.display = 'none';
} else {
categorySection.style.display = '';
}
});
// Update results status
this.hasResults = query === '' || anyResults;
this.visibleCount = visibleCount;
}
2025-05-03 18:22:25 -07:00
}));
2025-05-03 18:22:25 -07:00
// Register searchArticles component
Alpine.data('searchArticles', () => ({
searchQuery: '',
hasResults: true,
visibleCount: 0,
loading: false,
focusedItemIndex: -1,
debounceTimeout: null,
2025-05-03 18:22:25 -07:00
init() {
// Initialize with loading state
this.loading = true;
// Initialize the visible count
this.visibleCount = document.querySelectorAll('.article-item').length;
// Setup watchers
this.$watch('searchQuery', (query) => {
// Debounce search for better performance
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout);
}
this.debounceTimeout = setTimeout(() => {
this.filterContent(query);
}, 150);
});
// Setup keyboard shortcuts
this.setupKeyboardShortcuts();
// Set loading to false after initialization
setTimeout(() => {
this.loading = false;
}, 300);
},
2025-05-03 18:22:25 -07:00
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// '/' key focuses the search input
if (e.key === '/' && document.activeElement.id !== 'app-search') {
e.preventDefault();
document.getElementById('app-search').focus();
}
// Escape key clears the search
if (e.key === 'Escape' && this.searchQuery !== '') {
this.searchQuery = '';
document.getElementById('app-search').focus();
this.focusedItemIndex = -1;
}
});
},
2025-05-03 18:22:25 -07:00
filterContent(query) {
query = query.toLowerCase().trim();
let anyResults = false;
let visibleCount = 0;
document.querySelectorAll('.article-item').forEach((item) => {
const title = (item.getAttribute('data-title') || '').toLowerCase();
const tags = (item.getAttribute('data-tags') || '').toLowerCase();
const description = (item.getAttribute('data-description') || '').toLowerCase();
const isMatch = query === '' ||
title.includes(query) ||
tags.includes(query) ||
description.includes(query);
if (isMatch) {
item.style.display = '';
anyResults = true;
visibleCount++;
2025-05-03 13:35:55 -07:00
} else {
2025-05-03 18:22:25 -07:00
item.style.display = 'none';
2025-05-03 13:35:55 -07:00
}
2025-05-03 18:22:25 -07:00
});
this.hasResults = query === '' || anyResults;
this.visibleCount = visibleCount;
}
}));
2025-05-03 13:35:55 -07:00
2025-05-03 18:22:25 -07:00
// Register searchProjects component
Alpine.data('searchProjects', () => ({
searchQuery: '',
hasResults: true,
visibleCount: 0,
loading: false,
focusedItemIndex: -1,
debounceTimeout: null,
2025-05-03 13:35:55 -07:00
2025-05-03 18:22:25 -07:00
init() {
// Initialize with loading state
this.loading = true;
// Initialize the visible count
this.visibleCount = document.querySelectorAll('.project-item').length;
// Setup watchers
this.$watch('searchQuery', (query) => {
// Debounce search for better performance
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout);
}
this.debounceTimeout = setTimeout(() => {
this.filterContent(query);
}, 150);
});
// Setup keyboard shortcuts
this.setupKeyboardShortcuts();
// Set loading to false after initialization
setTimeout(() => {
this.loading = false;
}, 300);
},
2025-05-03 13:35:55 -07:00
2025-05-03 18:22:25 -07:00
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// '/' key focuses the search input
if (e.key === '/' && document.activeElement.id !== 'app-search') {
e.preventDefault();
document.getElementById('app-search').focus();
}
// Escape key clears the search
if (e.key === 'Escape' && this.searchQuery !== '') {
this.searchQuery = '';
document.getElementById('app-search').focus();
this.focusedItemIndex = -1;
}
});
},
2025-05-03 13:35:55 -07:00
2025-05-03 18:22:25 -07:00
filterContent(query) {
query = query.toLowerCase().trim();
let anyResults = false;
let visibleCount = 0;
document.querySelectorAll('.project-item').forEach((item) => {
const title = (item.getAttribute('data-title') || '').toLowerCase();
const tags = (item.getAttribute('data-tags') || '').toLowerCase();
const description = (item.getAttribute('data-description') || '').toLowerCase();
const github = (item.getAttribute('data-github') || '').toLowerCase();
const live = (item.getAttribute('data-live') || '').toLowerCase();
const isMatch = query === '' ||
title.includes(query) ||
tags.includes(query) ||
description.includes(query) ||
github.includes(query) ||
live.includes(query);
if (isMatch) {
item.style.display = '';
anyResults = true;
visibleCount++;
} else {
2025-05-03 18:22:25 -07:00
item.style.display = 'none';
}
2025-05-03 18:22:25 -07:00
});
this.hasResults = query === '' || anyResults;
this.visibleCount = visibleCount;
}
}));
}
2025-05-03 01:35:48 -07:00
});
</script>