feat: add Shaarli Pro theme with new header, styling, and JavaScript.

This commit is contained in:
2026-01-14 12:29:13 -05:00
parent 16a76db547
commit 7fdf3edcf3
3 changed files with 358 additions and 13 deletions
+205 -9
View File
@@ -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('<strong>private</strong> links');
} else if (isPublicActive) {
filterParts.push('<strong>public</strong> links');
}
if (isUntaggedActive) {
if (filterParts.length > 0) {
filterParts.push('without tags');
} else {
filterParts.push('links <strong>without tags</strong>');
}
}
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 = `
<div class="filter-info-content">
<i class="mdi mdi-filter-variant"></i>
<span>${message}</span>
</div>
<button type="button" class="filter-clear-btn" id="filter-clear-btn" title="Clear filters">
<i class="mdi mdi-close"></i>
<span>Clear</span>
</button>
`;
// Try to find the best insertion point
const contentToolbar = document.querySelector('.content-toolbar');
const linklist = document.getElementById('linklist');
const linksList = document.getElementById('links-list');
console.log('[Filter Debug] contentToolbar:', contentToolbar);
console.log('[Filter Debug] linklist:', linklist);
console.log('[Filter Debug] linksList:', linksList);
// Insert after the content-toolbar (pagination)
if (contentToolbar && contentToolbar.parentNode) {
contentToolbar.parentNode.insertBefore(banner, contentToolbar.nextSibling);
console.log('[Filter Debug] Banner inserted after content-toolbar');
} else if (linksList && linksList.parentNode) {
linksList.parentNode.insertBefore(banner, linksList);
console.log('[Filter Debug] Banner inserted before links-list');
} else if (linklist) {
linklist.insertBefore(banner, linklist.firstChild);
console.log('[Filter Debug] Banner inserted at beginning of linklist');
} else {
console.log('[Filter Debug] Could not find insertion point for banner');
}
// Add click handler to clear button
document.getElementById('filter-clear-btn')?.addEventListener('click', () => {
localStorage.removeItem('shaarliFilterState');
console.log('[Filter] Cleared filter state from localStorage');
window.location.href = basePath + '/';
});
}
}
})();
// Handle links per page options
@@ -421,7 +617,7 @@ document.addEventListener('DOMContentLoaded', () => {
e.preventDefault();
const value = filterInput.value;
if (value && value > 0) {
window.location.href = shaarli.basePath + '/?nb=' + value;
window.location.href = shaarli.basePath + '/links-per-page?nb=' + value;
}
});
}