diff --git a/shaarli-pro/css/style.css b/shaarli-pro/css/style.css index bda2572..e713308 100644 --- a/shaarli-pro/css/style.css +++ b/shaarli-pro/css/style.css @@ -1076,6 +1076,12 @@ input:checked+.theme-slider:before { color: var(--text-main); } +.paging-total { + color: var(--text-muted); + font-size: 0.85rem; + margin-left: 0.25rem; +} + .paging a { display: flex; align-items: center; @@ -2813,7 +2819,6 @@ select:focus { } } -/* ===== Print Styles ===== */ @media print { .sidebar, @@ -2832,4 +2837,148 @@ select:focus { box-shadow: none; border: 1px solid #ddd; } +} + +/* ===== Filter Active Indicator ===== */ +.header-action-btn.has-active-filter { + position: relative; + background: rgba(59, 130, 246, 0.3); + border: 1px solid rgba(59, 130, 246, 0.5); +} + +.header-action-btn.has-active-filter:hover { + background: rgba(59, 130, 246, 0.4); +} + +.filter-badge { + position: absolute; + top: 4px; + right: 4px; + width: 8px; + height: 8px; + background: #ef4444; + border-radius: 50%; + border: 2px solid var(--header-bg); + animation: pulse-badge 2s infinite; +} + +@keyframes pulse-badge { + + 0%, + 100% { + transform: scale(1); + opacity: 1; + } + + 50% { + transform: scale(1.2); + opacity: 0.8; + } +} + +/* ===== Filter Info Banner ===== */ +.filter-info-banner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.875rem 1.25rem; + margin-bottom: 1rem; + background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(99, 102, 241, 0.08)); + border: 1px solid rgba(59, 130, 246, 0.25); + border-radius: 0.75rem; + color: var(--text-main); + animation: slideInBanner 0.3s ease; +} + +[data-theme="dark"] .filter-info-banner { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.15), rgba(99, 102, 241, 0.12)); + border-color: rgba(59, 130, 246, 0.3); +} + +@keyframes slideInBanner { + from { + opacity: 0; + transform: translateY(-10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.filter-info-content { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 0.9rem; +} + +.filter-info-content i { + font-size: 1.25rem; + color: #3b82f6; +} + +.filter-info-content strong { + color: #3b82f6; + font-weight: 600; +} + +[data-theme="dark"] .filter-info-content i, +[data-theme="dark"] .filter-info-content strong { + color: #60a5fa; +} + +.filter-clear-btn { + display: flex; + align-items: center; + gap: 0.375rem; + padding: 0.5rem 0.875rem; + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.25); + border-radius: 0.5rem; + color: #ef4444; + font-size: 0.8125rem; + font-weight: 500; + font-family: inherit; + text-decoration: none; + transition: all 0.2s ease; + white-space: nowrap; + cursor: pointer; +} + +.filter-clear-btn:hover { + background: rgba(239, 68, 68, 0.2); + border-color: rgba(239, 68, 68, 0.4); + color: #dc2626; +} + +[data-theme="dark"] .filter-clear-btn { + background: rgba(239, 68, 68, 0.15); + border-color: rgba(239, 68, 68, 0.3); + color: #f87171; +} + +[data-theme="dark"] .filter-clear-btn:hover { + background: rgba(239, 68, 68, 0.25); + color: #fca5a5; +} + +.filter-clear-btn i { + font-size: 1rem; +} + +/* Responsive adjustments for filter banner */ +@media (max-width: 600px) { + .filter-info-banner { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + padding: 0.75rem 1rem; + } + + .filter-clear-btn { + align-self: flex-end; + } } \ No newline at end of file diff --git a/shaarli-pro/js/script.js b/shaarli-pro/js/script.js index 08df9ba..9250a32 100644 --- a/shaarli-pro/js/script.js +++ b/shaarli-pro/js/script.js @@ -401,16 +401,212 @@ document.addEventListener('DOMContentLoaded', () => { } }); - // Handle filter toggle switches - using data-url like the example - document.querySelectorAll('.toggle-switch input[type="checkbox"]').forEach(checkbox => { - checkbox.addEventListener('change', (e) => { - const label = e.target.closest('label'); - if (label && label.dataset.url) { - window.location.href = label.dataset.url; - } - }); + // Handle filter toggle switches + function applyFilters() { + const isPrivate = filterPrivate?.checked || false; + const isPublic = filterPublic?.checked || false; + const isUntagged = filterUntagged?.checked || false; + + let basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : ''; + let url = basePath + '/'; + + // Save filter state to localStorage before navigation + const filterState = { + private: isPrivate, + public: isPublic, + untagged: isUntagged, + timestamp: Date.now() + }; + localStorage.setItem('shaarliFilterState', JSON.stringify(filterState)); + console.log('[Filter] Saved filter state:', filterState); + + // Build the URL based on selected filters using the correct Shaarli admin paths + if (isPrivate && isUntagged) { + url = basePath + '/admin/visibility/private?searchtags='; + } else if (isPublic && isUntagged) { + url = basePath + '/admin/visibility/public?searchtags='; + } else if (isPrivate) { + url = basePath + '/admin/visibility/private'; + } else if (isPublic) { + url = basePath + '/admin/visibility/public'; + } else if (isUntagged) { + url = basePath + '/untagged-only'; + } else { + // No filter selected - clear the state + localStorage.removeItem('shaarliFilterState'); + } + + console.log('[Filter] Navigating to:', url); + window.location.href = url; + } + + // Clear filters function + function clearFilters() { + localStorage.removeItem('shaarliFilterState'); + if (filterPrivate) filterPrivate.checked = false; + if (filterPublic) filterPublic.checked = false; + if (filterUntagged) filterUntagged.checked = false; + } + + // Private and public are mutually exclusive + filterPrivate?.addEventListener('change', (e) => { + if (e.target.checked && filterPublic) { + filterPublic.checked = false; + } + applyFilters(); }); + filterPublic?.addEventListener('change', (e) => { + if (e.target.checked && filterPrivate) { + filterPrivate.checked = false; + } + applyFilters(); + }); + + filterUntagged?.addEventListener('change', () => { + applyFilters(); + }); + + // Initialize filter states from localStorage (since Shaarli redirects after applying filters) + (function initFilterStates() { + console.log('[Filter Debug] ========================================'); + + // Try to read filter state from localStorage + let savedState = null; + try { + const savedJson = localStorage.getItem('shaarliFilterState'); + if (savedJson) { + savedState = JSON.parse(savedJson); + console.log('[Filter Debug] Found saved filter state:', savedState); + + // Check if state is not too old (30 seconds max) + const age = Date.now() - (savedState.timestamp || 0); + if (age > 30000) { + console.log('[Filter Debug] Saved state is too old, ignoring'); + localStorage.removeItem('shaarliFilterState'); + savedState = null; + } + } + } catch (e) { + console.log('[Filter Debug] Error reading saved state:', e); + } + + // Determine active filters from saved state + let isPrivateActive = savedState?.private || false; + let isPublicActive = savedState?.public || false; + let isUntaggedActive = savedState?.untagged || false; + + console.log('[Filter Debug] isPrivateActive:', isPrivateActive); + console.log('[Filter Debug] isPublicActive:', isPublicActive); + console.log('[Filter Debug] isUntaggedActive:', isUntaggedActive); + + // Set checkbox states + if (filterPrivate && isPrivateActive) { + filterPrivate.checked = true; + console.log('[Filter Debug] ✓ Set filterPrivate to CHECKED'); + } + if (filterPublic && isPublicActive) { + filterPublic.checked = true; + console.log('[Filter Debug] ✓ Set filterPublic to CHECKED'); + } + if (filterUntagged && isUntaggedActive) { + filterUntagged.checked = true; + console.log('[Filter Debug] ✓ Set filterUntagged to CHECKED'); + } + + const hasActiveFilter = isPrivateActive || isPublicActive || isUntaggedActive; + console.log('[Filter Debug] hasActiveFilter:', hasActiveFilter); + console.log('[Filter Debug] ========================================'); + + // Add/update filter indicator badge on the filter button + if (filterToggleBtn && hasActiveFilter) { + filterToggleBtn.classList.add('has-active-filter'); + console.log('[Filter Debug] Added has-active-filter class to button'); + + // Add badge indicator if not exists + if (!filterToggleBtn.querySelector('.filter-badge')) { + const badge = document.createElement('span'); + badge.className = 'filter-badge'; + filterToggleBtn.appendChild(badge); + console.log('[Filter Debug] Added filter badge'); + } + } + + // Create and display the filter info banner + if (hasActiveFilter && !document.getElementById('filter-info-banner')) { + // Build the message - describe the active filter without count + let filterParts = []; + + if (isPrivateActive) { + filterParts.push('private links'); + } else if (isPublicActive) { + filterParts.push('public links'); + } + + if (isUntaggedActive) { + if (filterParts.length > 0) { + filterParts.push('without tags'); + } else { + filterParts.push('links without tags'); + } + } + + let message = 'Showing ' + filterParts.join(' '); + + console.log('[Filter Debug] filterParts:', filterParts); + console.log('[Filter Debug] message:', message); + + if (filterParts.length > 0) { + // Get base path safely + const basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : ''; + + const banner = document.createElement('div'); + banner.id = 'filter-info-banner'; + banner.className = 'filter-info-banner'; + banner.innerHTML = ` +