388 lines
16 KiB
JavaScript
388 lines
16 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();
|
|
|
|
// 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);
|
|
});
|
|
}
|
|
} |