first commit
This commit is contained in:
@@ -0,0 +1,456 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// ===== Theme Toggle =====
|
||||
const themeCheckbox = document.getElementById('theme-toggle-checkbox');
|
||||
const themeIconLight = document.getElementById('theme-icon-light');
|
||||
const themeLabelSpan = document.querySelector('.theme-toggle-label span');
|
||||
|
||||
function updateTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
|
||||
if (themeCheckbox) {
|
||||
themeCheckbox.checked = theme === 'dark';
|
||||
}
|
||||
if (themeIconLight) {
|
||||
themeIconLight.className = theme === 'dark' ? 'mdi mdi-weather-night' : 'mdi mdi-weather-sunny';
|
||||
}
|
||||
if (themeLabelSpan) {
|
||||
themeLabelSpan.textContent = theme === 'dark' ? 'Dark Mode' : 'Light Mode';
|
||||
}
|
||||
}
|
||||
|
||||
// Init Theme
|
||||
const savedTheme = localStorage.getItem('theme') ||
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
updateTheme(savedTheme);
|
||||
|
||||
if (themeCheckbox) {
|
||||
themeCheckbox.addEventListener('change', () => {
|
||||
const next = themeCheckbox.checked ? 'dark' : 'light';
|
||||
updateTheme(next);
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Mobile Sidebar Toggle =====
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebar?.classList.toggle('show');
|
||||
sidebarOverlay?.classList.toggle('show');
|
||||
}
|
||||
|
||||
mobileMenuBtn?.addEventListener('click', toggleSidebar);
|
||||
sidebarOverlay?.addEventListener('click', toggleSidebar);
|
||||
|
||||
// ===== Search Overlay =====
|
||||
const searchOverlay = document.getElementById('search-overlay');
|
||||
const searchToggleBtn = document.getElementById('search-toggle-btn');
|
||||
const searchModalInput = document.getElementById('search-modal-input');
|
||||
|
||||
function openSearch() {
|
||||
searchOverlay?.classList.add('show');
|
||||
setTimeout(() => searchModalInput?.focus(), 100);
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
searchOverlay?.classList.remove('show');
|
||||
}
|
||||
|
||||
searchToggleBtn?.addEventListener('click', openSearch);
|
||||
|
||||
// Close search on overlay click
|
||||
searchOverlay?.addEventListener('click', (e) => {
|
||||
if (e.target === searchOverlay) {
|
||||
closeSearch();
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard shortcut: S to open search, ESC to close
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// Don't trigger if typing in an input
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
|
||||
if (e.key === 'Escape') {
|
||||
closeSearch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 's' || e.key === 'S') {
|
||||
e.preventDefault();
|
||||
openSearch();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
closeSearch();
|
||||
closeFilterPanel();
|
||||
}
|
||||
});
|
||||
|
||||
// ===== Filter Panel =====
|
||||
const filterToggleBtn = document.getElementById('filter-toggle-btn');
|
||||
const filterPanel = document.getElementById('filter-panel');
|
||||
const filterCloseBtn = document.getElementById('filter-close-btn');
|
||||
const filterPrivate = document.getElementById('filter-private');
|
||||
const filterPublic = document.getElementById('filter-public');
|
||||
const filterUntagged = document.getElementById('filter-untagged');
|
||||
|
||||
function toggleFilterPanel() {
|
||||
filterPanel?.classList.toggle('show');
|
||||
}
|
||||
|
||||
function closeFilterPanel() {
|
||||
filterPanel?.classList.remove('show');
|
||||
}
|
||||
|
||||
filterToggleBtn?.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
toggleFilterPanel();
|
||||
});
|
||||
|
||||
filterCloseBtn?.addEventListener('click', closeFilterPanel);
|
||||
|
||||
// Close filter when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (filterPanel?.classList.contains('show')) {
|
||||
if (!filterPanel.contains(e.target) && e.target !== filterToggleBtn) {
|
||||
closeFilterPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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 links per page options
|
||||
|
||||
|
||||
// Handle custom value form submission
|
||||
const filterInput = document.querySelector('.filter-input');
|
||||
if (filterInput) {
|
||||
filterInput.closest('form')?.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const value = filterInput.value;
|
||||
if (value && value > 0) {
|
||||
window.location.href = shaarli.basePath + '/?nb=' + value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ===== View Toggle (Grid/List/Compact) =====
|
||||
const linksList = document.getElementById('links-list');
|
||||
const viewGridBtn = document.getElementById('view-grid-btn');
|
||||
const viewListBtn = document.getElementById('view-list-btn');
|
||||
const viewCompactBtn = document.getElementById('view-compact-btn');
|
||||
|
||||
function setView(view) {
|
||||
if (!linksList) return;
|
||||
|
||||
// Remove all view classes
|
||||
linksList.classList.remove('view-grid', 'view-list', 'view-compact');
|
||||
|
||||
// Remove active state from all buttons
|
||||
viewGridBtn?.classList.remove('active');
|
||||
viewListBtn?.classList.remove('active');
|
||||
viewCompactBtn?.classList.remove('active');
|
||||
|
||||
// Apply selected view
|
||||
if (view === 'list') {
|
||||
linksList.classList.add('view-list');
|
||||
viewListBtn?.classList.add('active');
|
||||
} else if (view === 'compact') {
|
||||
linksList.classList.add('view-compact');
|
||||
viewCompactBtn?.classList.add('active');
|
||||
} else {
|
||||
// Default to grid
|
||||
linksList.classList.add('view-grid');
|
||||
viewGridBtn?.classList.add('active');
|
||||
}
|
||||
|
||||
localStorage.setItem('linksView', view);
|
||||
}
|
||||
|
||||
// Init view from localStorage
|
||||
const savedView = localStorage.getItem('linksView') || 'grid';
|
||||
setView(savedView);
|
||||
|
||||
viewGridBtn?.addEventListener('click', () => setView('grid'));
|
||||
viewListBtn?.addEventListener('click', () => setView('list'));
|
||||
viewCompactBtn?.addEventListener('click', () => setView('compact'));
|
||||
|
||||
// ===== Multi-Select Mode =====
|
||||
const selectModeBtn = document.getElementById('select-mode-btn');
|
||||
const bulkActionsBar = document.getElementById('bulk-actions-bar');
|
||||
const bulkCount = document.getElementById('bulk-count');
|
||||
const bulkSelectAll = document.getElementById('bulk-select-all');
|
||||
const bulkCancel = document.getElementById('bulk-cancel');
|
||||
const bulkDelete = document.getElementById('bulk-delete');
|
||||
const bulkPublic = document.getElementById('bulk-public');
|
||||
const bulkPrivate = document.getElementById('bulk-private');
|
||||
|
||||
let selectionMode = false;
|
||||
let selectedIds = new Set();
|
||||
|
||||
function updateBulkUI() {
|
||||
if (bulkCount) {
|
||||
bulkCount.textContent = selectedIds.size;
|
||||
}
|
||||
|
||||
// Update link cards visual state
|
||||
document.querySelectorAll('.link-outer').forEach(card => {
|
||||
const id = card.dataset.id;
|
||||
if (selectedIds.has(id)) {
|
||||
card.classList.add('selected');
|
||||
} else {
|
||||
card.classList.remove('selected');
|
||||
}
|
||||
});
|
||||
|
||||
// Update checkboxes
|
||||
document.querySelectorAll('.link-checkbox').forEach(cb => {
|
||||
cb.checked = selectedIds.has(cb.dataset.id);
|
||||
});
|
||||
}
|
||||
|
||||
function enterSelectionMode() {
|
||||
selectionMode = true;
|
||||
document.body.classList.add('selection-mode');
|
||||
bulkActionsBar?.classList.add('show');
|
||||
selectModeBtn?.classList.add('active');
|
||||
}
|
||||
|
||||
function exitSelectionMode() {
|
||||
selectionMode = false;
|
||||
selectedIds.clear();
|
||||
document.body.classList.remove('selection-mode');
|
||||
bulkActionsBar?.classList.remove('show');
|
||||
selectModeBtn?.classList.remove('active');
|
||||
updateBulkUI();
|
||||
}
|
||||
|
||||
function toggleSelection(id) {
|
||||
if (selectedIds.has(id)) {
|
||||
selectedIds.delete(id);
|
||||
} else {
|
||||
selectedIds.add(id);
|
||||
}
|
||||
|
||||
if (selectedIds.size > 0 && !selectionMode) {
|
||||
enterSelectionMode();
|
||||
} else if (selectedIds.size === 0 && selectionMode) {
|
||||
exitSelectionMode();
|
||||
}
|
||||
|
||||
updateBulkUI();
|
||||
}
|
||||
|
||||
selectModeBtn?.addEventListener('click', () => {
|
||||
if (selectionMode) {
|
||||
exitSelectionMode();
|
||||
} else {
|
||||
enterSelectionMode();
|
||||
}
|
||||
});
|
||||
|
||||
bulkCancel?.addEventListener('click', exitSelectionMode);
|
||||
|
||||
bulkSelectAll?.addEventListener('click', () => {
|
||||
document.querySelectorAll('.link-outer').forEach(card => {
|
||||
if (card.dataset.id) {
|
||||
selectedIds.add(card.dataset.id);
|
||||
}
|
||||
});
|
||||
updateBulkUI();
|
||||
});
|
||||
|
||||
// Handle checkbox clicks
|
||||
document.addEventListener('change', (e) => {
|
||||
if (e.target.classList.contains('link-checkbox')) {
|
||||
toggleSelection(e.target.dataset.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle card clicks in selection mode
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!selectionMode) return;
|
||||
|
||||
const card = e.target.closest('.link-outer');
|
||||
if (card && card.dataset.id) {
|
||||
// Don't toggle if clicking on actions or links
|
||||
if (e.target.closest('.link-actions') ||
|
||||
e.target.closest('.link-hover-actions') ||
|
||||
e.target.tagName === 'A') {
|
||||
return;
|
||||
}
|
||||
toggleSelection(card.dataset.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Bulk actions
|
||||
bulkDelete?.addEventListener('click', () => {
|
||||
if (selectedIds.size === 0) return;
|
||||
if (!confirm(`Delete ${selectedIds.size} bookmark(s)?`)) return;
|
||||
|
||||
// Submit form with selected IDs
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = shaarli.basePath + '/admin/shaare/delete';
|
||||
|
||||
selectedIds.forEach(id => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'id[]';
|
||||
input.value = id;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
const tokenInput = document.createElement('input');
|
||||
tokenInput.type = 'hidden';
|
||||
tokenInput.name = 'token';
|
||||
tokenInput.value = document.querySelector('input[name="token"]')?.value || '';
|
||||
form.appendChild(tokenInput);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
});
|
||||
|
||||
bulkPublic?.addEventListener('click', () => {
|
||||
if (selectedIds.size === 0) return;
|
||||
bulkVisibilityChange('public');
|
||||
});
|
||||
|
||||
bulkPrivate?.addEventListener('click', () => {
|
||||
if (selectedIds.size === 0) return;
|
||||
bulkVisibilityChange('private');
|
||||
});
|
||||
|
||||
function bulkVisibilityChange(visibility) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = shaarli.basePath + '/admin/shaare/visibility';
|
||||
|
||||
selectedIds.forEach(id => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'id[]';
|
||||
input.value = id;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
const visInput = document.createElement('input');
|
||||
visInput.type = 'hidden';
|
||||
visInput.name = 'visibility';
|
||||
visInput.value = visibility;
|
||||
form.appendChild(visInput);
|
||||
|
||||
const tokenInput = document.createElement('input');
|
||||
tokenInput.type = 'hidden';
|
||||
tokenInput.name = 'token';
|
||||
tokenInput.value = document.querySelector('input[name="token"]')?.value || '';
|
||||
form.appendChild(tokenInput);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// ===== Search Type Toggle =====
|
||||
const searchTagsBtn = document.getElementById('search-tags-btn');
|
||||
const searchAllBtn = document.getElementById('search-all-btn');
|
||||
const searchForm = document.getElementById('search-form');
|
||||
|
||||
searchTagsBtn?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
searchModalInput.name = 'searchtags';
|
||||
searchAllBtn?.classList.remove('active');
|
||||
searchTagsBtn?.classList.add('active');
|
||||
searchForm?.submit();
|
||||
});
|
||||
|
||||
searchAllBtn?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
searchModalInput.name = 'searchterm';
|
||||
searchTagsBtn?.classList.remove('active');
|
||||
searchAllBtn?.classList.add('active');
|
||||
searchForm?.submit();
|
||||
});
|
||||
|
||||
// ===== Thumbnail Update =====
|
||||
const thumbnailsPage = document.querySelector('.page-thumbnails');
|
||||
if (thumbnailsPage) {
|
||||
const thumbnailPlaceholder = document.querySelector('.thumbnail-placeholder');
|
||||
const thumbnailTitle = document.querySelector('.thumbnail-link-title');
|
||||
const progressCurrent = document.querySelector('.progress-current');
|
||||
const progressBarActual = document.querySelector('.progress-actual');
|
||||
const idsInput = document.querySelector('input[name="ids"]');
|
||||
|
||||
if (idsInput && idsInput.value) {
|
||||
const thumbnailsIdList = idsInput.value.split(',');
|
||||
const total = thumbnailsIdList.length;
|
||||
let i = 0;
|
||||
|
||||
const updateThumbnail = function (id) {
|
||||
console.log('Updating thumbnail #' + i + ' with id ' + id);
|
||||
|
||||
fetch(shaarli.basePath + '/admin/shaare/' + id + '/update-thumbnail', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
i++;
|
||||
|
||||
if (thumbnailTitle) {
|
||||
thumbnailTitle.textContent = data.title || 'Processing...';
|
||||
}
|
||||
|
||||
if (thumbnailPlaceholder) {
|
||||
if (data.thumbnail) {
|
||||
thumbnailPlaceholder.innerHTML = '<img title="Current thumbnail" src="' + data.thumbnail + '" style="max-width: 100%; height: auto;"/>';
|
||||
} else {
|
||||
thumbnailPlaceholder.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (progressCurrent) {
|
||||
progressCurrent.textContent = i;
|
||||
}
|
||||
|
||||
if (progressBarActual) {
|
||||
progressBarActual.style.width = ((i * 100) / total) + '%';
|
||||
}
|
||||
|
||||
if (i < total) {
|
||||
updateThumbnail(thumbnailsIdList[i]);
|
||||
} else {
|
||||
if (thumbnailTitle) {
|
||||
thumbnailTitle.textContent = 'Thumbnail update done!';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to update thumbnail:', error);
|
||||
if (thumbnailTitle) {
|
||||
thumbnailTitle.textContent = 'Error updating thumbnail #' + i;
|
||||
}
|
||||
// Continue with next thumbnail even if one fails
|
||||
i++;
|
||||
if (i < total) {
|
||||
updateThumbnail(thumbnailsIdList[i]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Start the update process
|
||||
updateThumbnail(thumbnailsIdList[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user