feat: Introduce Shaarli Professional theme with modern sidebar layout, light/dark mode, and associated JavaScript.

This commit is contained in:
Bruno Charest 2026-01-17 09:50:32 -05:00
parent 0bdf95e00a
commit 646f92005f
2 changed files with 54 additions and 50 deletions

View File

@ -3093,3 +3093,23 @@ select:focus {
font-size: 14px; font-size: 14px;
} }
/* Filter Badge Indicator */
.filter-badge {
position: absolute;
top: 2px;
right: 2px;
width: 8px;
height: 8px;
background: #ef4444;
border-radius: 50%;
border: 2px solid var(--header-bg);
}
.header-action-btn {
position: relative;
}
.header-action-btn.has-active-filter {
background: rgba(239, 68, 68, 0.2);
}

View File

@ -410,15 +410,7 @@ document.addEventListener('DOMContentLoaded', () => {
let basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : ''; let basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : '';
let url = basePath + '/'; let url = basePath + '/';
// Save filter state to localStorage before navigation console.log('[Filter] Applying filters - private:', isPrivate, 'public:', isPublic, 'untagged:', isUntagged);
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 // Build the URL based on selected filters using the correct Shaarli admin paths
if (isPrivate && isUntagged) { if (isPrivate && isUntagged) {
@ -430,22 +422,18 @@ document.addEventListener('DOMContentLoaded', () => {
} else if (isPublic) { } else if (isPublic) {
url = basePath + '/admin/visibility/public'; url = basePath + '/admin/visibility/public';
} else if (isUntagged) { } else if (isUntagged) {
url = basePath + '/untagged-only'; url = basePath + '/?searchtags=';
} else {
// No filter selected - clear the state
localStorage.removeItem('shaarliFilterState');
} }
// else: no filter, url stays as basePath + '/'
console.log('[Filter] Navigating to:', url); console.log('[Filter] Navigating to:', url);
window.location.href = url; window.location.href = url;
} }
// Clear filters function // Clear all filters and go to home
function clearFilters() { function clearAllFilters() {
localStorage.removeItem('shaarliFilterState'); const basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : '';
if (filterPrivate) filterPrivate.checked = false; window.location.href = basePath + '/';
if (filterPublic) filterPublic.checked = false;
if (filterUntagged) filterUntagged.checked = false;
} }
// Private and public are mutually exclusive // Private and public are mutually exclusive
@ -467,34 +455,24 @@ document.addEventListener('DOMContentLoaded', () => {
applyFilters(); applyFilters();
}); });
// Initialize filter states from localStorage (since Shaarli redirects after applying filters) // Initialize filter states from URL (detect current filter state from page URL)
(function initFilterStates() { (function initFilterStates() {
console.log('[Filter Debug] ========================================'); console.log('[Filter Debug] ========================================');
// Try to read filter state from localStorage // Detect filter state from current URL path
let savedState = null; const currentPath = window.location.pathname;
try { const currentSearch = window.location.search;
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) // Detect visibility filter from URL path
const age = Date.now() - (savedState.timestamp || 0); let isPrivateActive = currentPath.includes('/visibility/private') || currentPath.includes('/admin/visibility/private');
if (age > 30000) { let isPublicActive = currentPath.includes('/visibility/public') || currentPath.includes('/admin/visibility/public');
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 // Detect untagged filter
let isPrivateActive = savedState?.private || false; let isUntaggedActive = currentPath.includes('/untagged-only') ||
let isPublicActive = savedState?.public || false; (currentSearch.includes('searchtags=') && !currentSearch.match(/searchtags=[^&]+/));
let isUntaggedActive = savedState?.untagged || false;
console.log('[Filter Debug] URL path:', currentPath);
console.log('[Filter Debug] URL search:', currentSearch);
console.log('[Filter Debug] isPrivateActive:', isPrivateActive); console.log('[Filter Debug] isPrivateActive:', isPrivateActive);
console.log('[Filter Debug] isPublicActive:', isPublicActive); console.log('[Filter Debug] isPublicActive:', isPublicActive);
@ -534,24 +512,31 @@ document.addEventListener('DOMContentLoaded', () => {
// Create and display the filter info banner // Create and display the filter info banner
if (hasActiveFilter && !document.getElementById('filter-info-banner')) { if (hasActiveFilter && !document.getElementById('filter-info-banner')) {
// Build the message - describe the active filter without count // Get result count from page
let resultCount = '';
const pagingStats = document.querySelector('.paging-stats strong:last-child');
if (pagingStats) {
resultCount = pagingStats.textContent;
}
// Build the message with result count like "X results with status private"
let filterParts = []; let filterParts = [];
if (isPrivateActive) { if (isPrivateActive) {
filterParts.push('<strong>private</strong> links'); filterParts.push('with status <strong>private</strong>');
} else if (isPublicActive) { } else if (isPublicActive) {
filterParts.push('<strong>public</strong> links'); filterParts.push('with status <strong>public</strong>');
} }
if (isUntaggedActive) { if (isUntaggedActive) {
if (filterParts.length > 0) { if (filterParts.length > 0) {
filterParts.push('without tags'); filterParts.push('and <strong>untagged</strong>');
} else { } else {
filterParts.push('links <strong>without tags</strong>'); filterParts.push('<strong>untagged</strong>');
} }
} }
let message = 'Showing ' + filterParts.join(' '); let message = resultCount ? resultCount + ' results ' + filterParts.join(' ') : 'Showing ' + filterParts.join(' ') + ' links';
console.log('[Filter Debug] filterParts:', filterParts); console.log('[Filter Debug] filterParts:', filterParts);
console.log('[Filter Debug] message:', message); console.log('[Filter Debug] message:', message);
@ -599,8 +584,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Add click handler to clear button // Add click handler to clear button
document.getElementById('filter-clear-btn')?.addEventListener('click', () => { document.getElementById('filter-clear-btn')?.addEventListener('click', () => {
localStorage.removeItem('shaarliFilterState'); console.log('[Filter] Clear button clicked, navigating to home');
console.log('[Filter] Cleared filter state from localStorage');
window.location.href = basePath + '/'; window.location.href = basePath + '/';
}); });
} }