574 lines
22 KiB
JavaScript
574 lines
22 KiB
JavaScript
// JavaScript for Bookmarkly
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Handle Edit Category Modal
|
|
const editCategoryModal = document.getElementById('editCategoryModal');
|
|
if (editCategoryModal) {
|
|
editCategoryModal.addEventListener('show.bs.modal', function (event) {
|
|
const button = event.relatedTarget;
|
|
const id = button.getAttribute('data-bs-id');
|
|
const name = button.getAttribute('data-bs-name');
|
|
|
|
const idInput = editCategoryModal.querySelector('#edit-category-id');
|
|
const nameInput = editCategoryModal.querySelector('#edit-category-name');
|
|
|
|
idInput.value = id;
|
|
nameInput.value = name;
|
|
});
|
|
}
|
|
|
|
// --- Bookmark Modals ---
|
|
const editBookmarkModal = document.getElementById('editBookmarkModal');
|
|
if (editBookmarkModal) {
|
|
editBookmarkModal.addEventListener('show.bs.modal', function (event) {
|
|
const button = event.relatedTarget;
|
|
const id = button.getAttribute('data-bs-id');
|
|
const title = button.getAttribute('data-bs-title');
|
|
const url = button.getAttribute('data-bs-url');
|
|
const category = button.getAttribute('data-bs-category');
|
|
const icon = button.getAttribute('data-bs-icon');
|
|
const iconSrc = button.getAttribute('data-bs-icon-src');
|
|
const favorite = button.getAttribute('data-bs-favorite');
|
|
|
|
const modalTitle = editBookmarkModal.querySelector('.modal-title');
|
|
const idInput = editBookmarkModal.querySelector('#edit-bookmark-id');
|
|
const titleInput = editBookmarkModal.querySelector('#edit-bookmark-title');
|
|
const urlInput = editBookmarkModal.querySelector('#edit-bookmark-url');
|
|
const categoryInput = editBookmarkModal.querySelector('#edit-bookmark-category');
|
|
const iconUrlInput = editBookmarkModal.querySelector('#edit-bookmark-icon-url');
|
|
const favoriteInput = editBookmarkModal.querySelector('#edit-bookmark-favorite');
|
|
|
|
const currentIconPreview = document.getElementById('current-icon-preview');
|
|
const currentIconContainer = document.getElementById('current-icon-container');
|
|
const removeCustomIconBtn = document.getElementById('remove-custom-icon');
|
|
const removeIconInput = document.getElementById('remove-icon-input');
|
|
|
|
modalTitle.textContent = 'Edit ' + title;
|
|
idInput.value = id;
|
|
titleInput.value = title;
|
|
urlInput.value = url;
|
|
categoryInput.value = category;
|
|
favoriteInput.checked = (favorite == '1');
|
|
|
|
// Handle icon display logic
|
|
currentIconContainer.style.display = 'none'; // Hide by default
|
|
|
|
// Handle text input for icon URL/domain
|
|
if (icon && icon.startsWith('data/images/custom/')) {
|
|
iconUrlInput.value = ''; // Clear if it's a custom icon
|
|
} else {
|
|
// For domains or full URLs, just show the value in the input.
|
|
iconUrlInput.value = icon;
|
|
}
|
|
|
|
// Handle current icon preview and remove button
|
|
if (icon && icon.startsWith('data/images/custom/')) {
|
|
currentIconPreview.src = iconSrc;
|
|
currentIconContainer.style.display = 'block';
|
|
removeIconInput.value = '0';
|
|
} else {
|
|
currentIconContainer.style.display = 'none';
|
|
}
|
|
|
|
removeCustomIconBtn.onclick = function() {
|
|
removeIconInput.value = '1';
|
|
currentIconContainer.style.display = 'none';
|
|
document.getElementById('edit-bookmark-icon-upload').value = '';
|
|
};
|
|
});
|
|
|
|
// Reset remove icon flag when modal is hidden
|
|
editBookmarkModal.addEventListener('hidden.bs.modal', function () {
|
|
document.getElementById('remove-icon-input').value = '0';
|
|
document.getElementById('edit-bookmark-icon-upload').value = ''; // Clear file input
|
|
});
|
|
}
|
|
|
|
// --- URL Formatting ---
|
|
function formatUrl(inputElement) {
|
|
if (!inputElement || !inputElement.value) return;
|
|
|
|
let url = inputElement.value.trim();
|
|
if (!url) return;
|
|
|
|
// Check if URL already has a protocol
|
|
if (!/^https?:\/\//i.test(url)) {
|
|
// Add https:// by default
|
|
url = 'https://' + url;
|
|
inputElement.value = url;
|
|
}
|
|
}
|
|
|
|
// --- Favicon Fetching ---
|
|
function fetchFavicon(url, targetInputId) {
|
|
if (!url) return;
|
|
try {
|
|
// We only want to store the domain, not the full generated URL
|
|
const domain = new URL(url).hostname;
|
|
const targetInput = document.getElementById(targetInputId);
|
|
if (targetInput) {
|
|
targetInput.value = domain;
|
|
}
|
|
} catch (e) {
|
|
console.error('Invalid URL for favicon fetching:', e);
|
|
const targetInput = document.getElementById(targetInputId);
|
|
if (targetInput) {
|
|
targetInput.value = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Event listener for fetching favicon in ADD modal
|
|
const fetchButtonAdd = document.getElementById('fetch-favicon-add');
|
|
const urlInputAdd = document.getElementById('bookmark-url');
|
|
if (fetchButtonAdd && urlInputAdd) {
|
|
// Auto-format URL with http/https
|
|
urlInputAdd.addEventListener('blur', function() {
|
|
formatUrl(urlInputAdd);
|
|
});
|
|
// Auto-fetch on change
|
|
urlInputAdd.addEventListener('change', function() {
|
|
fetchFavicon(urlInputAdd.value, 'bookmark-icon-url');
|
|
});
|
|
// Manual fetch on button click
|
|
fetchButtonAdd.addEventListener('click', function() {
|
|
fetchFavicon(urlInputAdd.value, 'bookmark-icon-url');
|
|
});
|
|
}
|
|
|
|
// Event listener for fetching favicon in EDIT modal
|
|
const fetchButtonEdit = document.getElementById('fetch-favicon-edit');
|
|
const urlInputEdit = document.getElementById('edit-bookmark-url');
|
|
if (fetchButtonEdit && urlInputEdit) {
|
|
// Auto-format URL with http/https
|
|
urlInputEdit.addEventListener('blur', function() {
|
|
formatUrl(urlInputEdit);
|
|
});
|
|
urlInputEdit.addEventListener('change', function() {
|
|
const iconContainer = document.getElementById('current-icon-container');
|
|
// Only fetch favicon if there is no custom icon currently set
|
|
if (iconContainer && iconContainer.style.display === 'none') {
|
|
fetchFavicon(urlInputEdit.value, 'edit-bookmark-icon-url');
|
|
}
|
|
});
|
|
fetchButtonEdit.addEventListener('click', function() {
|
|
fetchFavicon(urlInputEdit.value, 'edit-bookmark-icon-url');
|
|
});
|
|
}
|
|
|
|
|
|
// --- Pexels API ---
|
|
const pexelsSearchButton = document.getElementById('pexels_search_button');
|
|
const pexelsSearchQuery = document.getElementById('pexels_search_query');
|
|
const pexelsResultsContainer = document.getElementById('pexels_results');
|
|
const bgUrlInput = document.getElementById('background_image_url');
|
|
|
|
if (pexelsSearchButton && pexelsSearchQuery && pexelsResultsContainer && bgUrlInput) {
|
|
const performSearch = () => {
|
|
const query = pexelsSearchQuery.value;
|
|
if (!query) {
|
|
alert('Please enter a search term.');
|
|
return;
|
|
}
|
|
|
|
pexelsResultsContainer.innerHTML = '<div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div>';
|
|
|
|
fetch(`api/pexels.php?query=${encodeURIComponent(query)}`, {
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
pexelsResultsContainer.innerHTML = '';
|
|
if (data.error) {
|
|
pexelsResultsContainer.innerHTML = `<div class="col-12"><div class="alert alert-danger">${data.error}</div></div>`;
|
|
return;
|
|
}
|
|
if (data.photos && data.photos.length > 0) {
|
|
data.photos.forEach(photo => {
|
|
const imgContainer = document.createElement('div');
|
|
imgContainer.className = 'col';
|
|
const img = document.createElement('img');
|
|
img.src = photo.src.medium;
|
|
img.className = 'img-thumbnail';
|
|
img.style.cursor = 'pointer';
|
|
img.alt = photo.alt;
|
|
img.addEventListener('click', () => {
|
|
bgUrlInput.value = photo.src.original;
|
|
// Optionally, show a preview
|
|
document.body.style.backgroundImage = `url(${photo.src.original})`;
|
|
pexelsResultsContainer.innerHTML = '<div class="col-12"><div class="alert alert-success">Background selected! Save theme settings to apply.</div></div>';
|
|
});
|
|
imgContainer.appendChild(img);
|
|
pexelsResultsContainer.appendChild(imgContainer);
|
|
});
|
|
} else {
|
|
pexelsResultsContainer.innerHTML = '<div class="col-12"><div class="alert alert-warning">No images found for your query.</div></div>';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching from Pexels API:', error);
|
|
pexelsResultsContainer.innerHTML = '<div class="col-12"><div class="alert alert-danger">An error occurred while searching.</div></div>';
|
|
});
|
|
};
|
|
|
|
pexelsSearchButton.addEventListener('click', performSearch);
|
|
pexelsSearchQuery.addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
performSearch();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Live preview for background settings
|
|
const brightnessSlider = document.getElementById('background_brightness');
|
|
const saturationSlider = document.getElementById('background_saturation');
|
|
const transparencySlider = document.getElementById('theme_transparency');
|
|
const themeSelector = document.getElementById('theme');
|
|
const transparencyContainer = document.getElementById('transparency-slider-container');
|
|
|
|
const updateTextColor = () => {
|
|
if (themeSelector && themeSelector.value !== 'dark' && brightnessSlider && brightnessSlider.value < 70) {
|
|
document.body.classList.add('dark-background-active');
|
|
} else {
|
|
document.body.classList.remove('dark-background-active');
|
|
}
|
|
};
|
|
|
|
const toggleTransparencySlider = () => {
|
|
if (themeSelector && transparencyContainer) {
|
|
transparencyContainer.style.display = themeSelector.value === 'transparent' ? 'block' : 'none';
|
|
}
|
|
};
|
|
|
|
// Initial check
|
|
toggleTransparencySlider();
|
|
|
|
if(themeSelector) {
|
|
themeSelector.addEventListener('change', () => {
|
|
toggleTransparencySlider();
|
|
updateTextColor();
|
|
});
|
|
}
|
|
|
|
if (brightnessSlider) {
|
|
brightnessSlider.addEventListener('input', (event) => {
|
|
document.documentElement.style.setProperty('--bg-brightness', `${event.target.value}%`);
|
|
updateTextColor();
|
|
});
|
|
}
|
|
|
|
if (saturationSlider) {
|
|
saturationSlider.addEventListener('input', (event) => {
|
|
document.documentElement.style.setProperty('--bg-saturation', `${event.target.value}%`);
|
|
});
|
|
}
|
|
|
|
if (transparencySlider) {
|
|
transparencySlider.addEventListener('input', (event) => {
|
|
document.documentElement.style.setProperty('--theme-transparency', event.target.value / 100);
|
|
});
|
|
}
|
|
|
|
// Initialize SortableJS for drag-and-drop
|
|
initSortable();
|
|
|
|
// Initialize search functionality
|
|
initSearch();
|
|
|
|
// Add form submit handlers to format URLs before submission
|
|
// Use event delegation to handle dynamically created forms
|
|
document.addEventListener('submit', function(event) {
|
|
const form = event.target;
|
|
if (form.tagName === 'FORM' && form.action.includes('bookmarks.php')) {
|
|
const actionInput = form.querySelector('input[name="action"]');
|
|
if (actionInput && (actionInput.value === 'add_bookmark' || actionInput.value === 'edit_bookmark')) {
|
|
const urlInput = form.querySelector('#bookmark-url') || form.querySelector('#edit-bookmark-url');
|
|
if (urlInput) {
|
|
formatUrl(urlInput);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
function initSortable() {
|
|
const categoryList = document.getElementById('category-list');
|
|
if (categoryList) {
|
|
new Sortable(categoryList, {
|
|
handle: '.sort-handle',
|
|
animation: 150,
|
|
onEnd: function (evt) {
|
|
const order = Array.from(evt.target.children).map(el => el.getAttribute('data-id'));
|
|
updateSortOrder('category', order);
|
|
}
|
|
});
|
|
}
|
|
|
|
const bookmarkLists = document.querySelectorAll('.bookmark-list');
|
|
bookmarkLists.forEach(list => {
|
|
new Sortable(list, {
|
|
handle: '.sort-handle',
|
|
animation: 150,
|
|
onEnd: function (evt) {
|
|
const order = Array.from(evt.target.children).map(el => el.getAttribute('data-id'));
|
|
updateSortOrder('bookmark', order);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function updateSortOrder(type, order) {
|
|
fetch('api/update_order.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: JSON.stringify({ type: type, order: order })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (!data.success) {
|
|
console.error('Failed to update sort order:', data.error);
|
|
// Optionally, show an error message to the user
|
|
}
|
|
})
|
|
.catch(error => console.error('Error updating sort order:', error));
|
|
}
|
|
|
|
function optimizeImage(path) {
|
|
if (confirm('Are you sure you want to optimize this image? The original file will be overwritten.')) {
|
|
const formData = new FormData();
|
|
formData.append('path', path);
|
|
|
|
console.log('Optimizing image:', path);
|
|
console.log('Current URL:', window.location.href);
|
|
console.log('Request URL:', new URL('api/optimize_image.php', window.location.href).href);
|
|
|
|
fetch('api/optimize_image.php', {
|
|
method: 'POST',
|
|
body: formData,
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(response => {
|
|
console.log('Response status:', response.status);
|
|
console.log('Response statusText:', response.statusText);
|
|
console.log('Response headers:', [...response.headers.entries()]);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status} - ${response.statusText}`);
|
|
}
|
|
|
|
return response.text(); // First get as text to see raw response
|
|
})
|
|
.then(text => {
|
|
console.log('Raw response:', text);
|
|
try {
|
|
const data = JSON.parse(text);
|
|
if (data.success) {
|
|
alert('Image optimized successfully. The page will now reload.');
|
|
window.location.reload();
|
|
} else {
|
|
alert('An error occurred: ' + data.message);
|
|
}
|
|
} catch (parseError) {
|
|
console.error('JSON parse error:', parseError);
|
|
console.error('Response text:', text);
|
|
alert('Invalid response from server. Check console for details.');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fetch error:', error);
|
|
console.error('Error stack:', error.stack);
|
|
alert('A network error occurred: ' + error.message);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Search functionality
|
|
function initSearch() {
|
|
const searchInput = document.getElementById('searchInput');
|
|
const searchButton = document.getElementById('searchButton');
|
|
const searchResetButton = document.getElementById('searchResetButton');
|
|
const searchResults = document.getElementById('searchResults');
|
|
|
|
// Check if search bar is enabled and elements exist
|
|
if (!searchInput || !searchResults) return;
|
|
|
|
let searchTimeout;
|
|
let allBookmarks = [];
|
|
|
|
// Load all bookmarks on page load
|
|
loadAllBookmarks();
|
|
|
|
// Search input event
|
|
searchInput.addEventListener('input', function() {
|
|
clearTimeout(searchTimeout);
|
|
const query = this.value.trim();
|
|
|
|
// Show/hide reset button based on input content
|
|
if (query.length > 0) {
|
|
searchResetButton.style.display = 'block';
|
|
} else {
|
|
searchResetButton.style.display = 'none';
|
|
}
|
|
|
|
if (query.length < 2) {
|
|
hideSearchResults();
|
|
return;
|
|
}
|
|
|
|
searchTimeout = setTimeout(() => {
|
|
performSearch(query);
|
|
}, 200);
|
|
});
|
|
|
|
// Search button click
|
|
if (searchButton) {
|
|
searchButton.addEventListener('click', function() {
|
|
const query = searchInput.value.trim();
|
|
if (query.length >= 2) {
|
|
performSearch(query);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Reset button click
|
|
if (searchResetButton) {
|
|
searchResetButton.addEventListener('click', function() {
|
|
searchInput.value = '';
|
|
searchResetButton.style.display = 'none';
|
|
hideSearchResults();
|
|
searchInput.focus();
|
|
});
|
|
}
|
|
|
|
|
|
// Hide search results when clicking outside
|
|
document.addEventListener('click', function(event) {
|
|
if (!searchInput.contains(event.target) && !searchResults.contains(event.target)) {
|
|
hideSearchResults();
|
|
}
|
|
});
|
|
|
|
// Keyboard shortcuts
|
|
document.addEventListener('keydown', function(event) {
|
|
// Ctrl+K to focus search
|
|
if (event.ctrlKey && event.key === 'k') {
|
|
event.preventDefault();
|
|
searchInput.focus();
|
|
}
|
|
|
|
// Escape to hide results
|
|
if (event.key === 'Escape') {
|
|
hideSearchResults();
|
|
searchInput.blur();
|
|
}
|
|
});
|
|
|
|
function loadAllBookmarks() {
|
|
// Extract bookmarks from the current page
|
|
const bookmarkElements = document.querySelectorAll('.bookmark-link, .list-group-item a');
|
|
allBookmarks = [];
|
|
|
|
bookmarkElements.forEach(element => {
|
|
const title = element.textContent.trim();
|
|
const url = element.href;
|
|
const icon = element.querySelector('img')?.src || 'include/images/default-icon.png';
|
|
|
|
// Find category name
|
|
let categoryName = '';
|
|
const categoryElement = element.closest('.category-column');
|
|
if (categoryElement) {
|
|
const categoryTitle = categoryElement.querySelector('h5');
|
|
if (categoryTitle) {
|
|
categoryName = categoryTitle.textContent.trim();
|
|
}
|
|
}
|
|
|
|
allBookmarks.push({
|
|
title: title,
|
|
url: url,
|
|
icon: icon,
|
|
category: categoryName
|
|
});
|
|
});
|
|
}
|
|
|
|
function performSearch(query) {
|
|
// Use API to search all bookmarks
|
|
fetch(`api/search.php?q=${encodeURIComponent(query)}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
console.error('Search error:', data.error);
|
|
displaySearchResults([], query);
|
|
return;
|
|
}
|
|
displaySearchResults(data.results || [], query);
|
|
})
|
|
.catch(error => {
|
|
console.error('Search failed:', error);
|
|
// Fallback to local search
|
|
const results = allBookmarks.filter(bookmark =>
|
|
bookmark.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
bookmark.url.toLowerCase().includes(query.toLowerCase()) ||
|
|
bookmark.category.toLowerCase().includes(query.toLowerCase())
|
|
);
|
|
displaySearchResults(results, query);
|
|
});
|
|
}
|
|
|
|
function displaySearchResults(results, query) {
|
|
searchResults.innerHTML = '';
|
|
|
|
if (results.length === 0) {
|
|
searchResults.innerHTML = `
|
|
<div class="search-no-results">
|
|
Geen bladwijzers gevonden voor "${query}"
|
|
</div>
|
|
`;
|
|
} else {
|
|
// Add bookmark results
|
|
results.forEach(bookmark => {
|
|
const resultItem = document.createElement('div');
|
|
resultItem.className = 'search-result-item';
|
|
resultItem.onclick = () => {
|
|
window.open(bookmark.url, '_blank');
|
|
hideSearchResults();
|
|
};
|
|
|
|
const categoryText = bookmark.category ? bookmark.category : '';
|
|
const tabText = bookmark.tab ? ` (${bookmark.tab})` : '';
|
|
const favoriteIcon = bookmark.is_favorite ? ' <i class="bi bi-star-fill text-warning"></i>' : '';
|
|
|
|
resultItem.innerHTML = `
|
|
<img src="${bookmark.icon}" alt="" class="bookmark-icon" onerror="this.onerror=null;this.src='include/images/default-icon.png';">
|
|
<div class="bookmark-info">
|
|
<div class="bookmark-title">${bookmark.title}${favoriteIcon}</div>
|
|
<div class="bookmark-url">${bookmark.url}</div>
|
|
</div>
|
|
${categoryText ? `<span class="bookmark-category">${categoryText}${tabText}</span>` : ''}
|
|
`;
|
|
|
|
searchResults.appendChild(resultItem);
|
|
});
|
|
}
|
|
|
|
showSearchResults();
|
|
}
|
|
|
|
function showSearchResults() {
|
|
searchResults.classList.add('show');
|
|
}
|
|
|
|
function hideSearchResults() {
|
|
searchResults.classList.remove('show');
|
|
}
|
|
}
|