diff --git a/api_v1_user.php b/api_v1_user.php
index c7468e3..22db499 100644
--- a/api_v1_user.php
+++ b/api_v1_user.php
@@ -1,28 +1,42 @@
date('Y-m-d H:i:s'),
+ 'method' => $_SERVER['REQUEST_METHOD'],
+ 'post' => $_POST,
+ 'session' => $_SESSION
+];
+
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = getCurrentUser();
if (!$user) {
+ $log['error'] = 'Unauthorized';
+ file_put_contents('requests.log', json_encode($log) . "\n", FILE_APPEND);
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
+ $log['user_id'] = $user['id'];
+
$username = !empty($_POST['username']) ? $_POST['username'] : $user['username'];
$avatar_url = isset($_POST['avatar_url']) ? $_POST['avatar_url'] : $user['avatar_url'];
- $dnd_mode = isset($_POST['dnd_mode']) ? (int)$_POST['dnd_mode'] : (int)($user['dnd_mode'] ?? 0);
- $sound_notifications = isset($_POST['sound_notifications']) ? (int)$_POST['sound_notifications'] : (int)($user['sound_notifications'] ?? 0);
- $theme = !empty($_POST['theme']) ? $_POST['theme'] : (!empty($user['theme']) ? $user['theme'] : 'dark');
+ $dnd_mode = isset($_POST['dnd_mode']) ? (int)$_POST['dnd_mode'] : 0;
+ $sound_notifications = isset($_POST['sound_notifications']) ? (int)$_POST['sound_notifications'] : 0;
+ $theme = !empty($_POST['theme']) ? $_POST['theme'] : $user['theme'];
try {
$stmt = db()->prepare("UPDATE users SET username = ?, avatar_url = ?, dnd_mode = ?, sound_notifications = ?, theme = ? WHERE id = ?");
- $stmt->execute([$username, $avatar_url, $dnd_mode, $sound_notifications, $theme, $user['id']]);
+ $success = $stmt->execute([$username, $avatar_url, $dnd_mode, $sound_notifications, $theme, $user['id']]);
+ $log['db_success'] = $success;
+ file_put_contents('requests.log', json_encode($log) . "\n", FILE_APPEND);
echo json_encode(['success' => true]);
} catch (Exception $e) {
+ $log['db_error'] = $e->getMessage();
+ file_put_contents('requests.log', json_encode($log) . "\n", FILE_APPEND);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
exit;
diff --git a/assets/js/main.js b/assets/js/main.js
index 42df985..a7b7f0f 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -121,9 +121,6 @@ document.addEventListener('DOMContentLoaded', () => {
div.onclick = (e) => {
if (e.target.closest('.emote-actions')) return;
navigator.clipboard.writeText(emote.code);
- const originalBg = div.style.backgroundColor;
- div.style.backgroundColor = 'var(--blurple)';
- setTimeout(() => div.style.backgroundColor = originalBg, 200);
};
div.querySelector('.delete-emote').onclick = async (e) => {
@@ -163,9 +160,6 @@ document.addEventListener('DOMContentLoaded', () => {
div.textContent = emoji;
div.onclick = () => {
navigator.clipboard.writeText(emoji);
- const originalBg = div.style.backgroundColor;
- div.style.backgroundColor = 'var(--blurple)';
- setTimeout(() => div.style.backgroundColor = originalBg, 200);
};
grid.appendChild(div);
});
@@ -179,10 +173,131 @@ document.addEventListener('DOMContentLoaded', () => {
btn.style.backgroundColor = idx === 0 ? 'var(--separator)' : 'transparent';
btn.innerHTML = ` ${cat}`;
- btn.onclick = () => {
- const sidebar_btns = picker.querySelectorAll('.emoji-sidebar button');
- sidebar_btns.forEach(b => b.style.backgroundColor = 'transparent');
+ btn.onclick = async () => {
+ sidebar.querySelectorAll('button').forEach(b => {
+ b.classList.remove('active');
+ b.style.backgroundColor = 'transparent';
+ });
+ btn.classList.add('active');
btn.style.backgroundColor = 'var(--separator)';
+ await renderGrid(cat);
+ };
+ sidebar.appendChild(btn);
+ });
+
+ searchInput.oninput = async () => {
+ const term = searchInput.value.trim();
+ if (term) {
+ sidebar.querySelectorAll('button').forEach(b => {
+ b.classList.remove('active');
+ b.style.backgroundColor = 'transparent';
+ });
+ await renderGrid(null, term);
+ } else {
+ const activeBtn = sidebar.querySelector('button.active');
+ const activeCat = activeBtn ? activeBtn.innerText.trim() : 'Custom';
+ await renderGrid(activeCat);
+ }
+ };
+
+ if (uploadInput) {
+ uploadInput.onchange = async () => {
+ const file = uploadInput.files[0];
+ if (!file) return;
+ const fd = new FormData();
+ fd.append('emote', file);
+ const res = await (await fetch('api/emotes.php?action=upload', { method: 'POST', body: fd })).json();
+ if (res.success) renderGrid('Custom');
+ else alert(res.error || "Upload failed");
+ };
+ }
+
+ await renderGrid('Custom');
+ }
+
+ const UniversalEmojiPicker = {
+ currentPicker: null,
+ show: async function(anchor, callback, options = {}) {
+ this.hide();
+ const picker = document.createElement('div');
+ picker.className = 'emoji-picker-container rounded shadow-lg p-0 d-flex flex-column';
+ picker.style.position = 'fixed';
+ picker.style.zIndex = '10000';
+ picker.style.width = options.width || '400px';
+ picker.style.height = options.height || '450px';
+ picker.style.backgroundColor = '#2b2d31';
+ picker.style.border = '1px solid #1e1f22';
+ picker.style.display = 'flex';
+ picker.style.flexDirection = 'column';
+
+ const mainLayout = document.createElement('div');
+ mainLayout.className = 'd-flex flex-grow-1 overflow-hidden';
+
+ const tabs = document.createElement('div');
+ tabs.className = 'emoji-sidebar d-flex flex-column p-2 border-end border-secondary custom-scrollbar';
+ tabs.style.width = '60px';
+ tabs.style.overflowY = 'auto';
+ tabs.style.backgroundColor = '#1e1f22';
+
+ const contentArea = document.createElement('div');
+ contentArea.className = 'd-flex flex-column flex-grow-1';
+
+ const searchContainer = document.createElement('div');
+ searchContainer.className = 'p-2 border-bottom border-secondary';
+ const searchInput = document.createElement('input');
+ searchInput.type = 'text';
+ searchInput.placeholder = 'Search emojis...';
+ searchInput.className = 'form-control form-control-sm bg-dark border-secondary text-white';
+ searchContainer.appendChild(searchInput);
+
+ const grid = document.createElement('div');
+ grid.className = 'emoji-grid flex-grow-1 p-2 overflow-auto custom-scrollbar';
+ grid.style.display = 'grid';
+ grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(36px, 1fr))';
+ grid.style.gap = '4px';
+
+ const categories = ['Custom', ...Object.keys(EMOJI_CATEGORIES)];
+
+ const renderGrid = async (category, searchTerm = '') => {
+ grid.innerHTML = '';
+ if (category === 'Custom' && !searchTerm) {
+ const emotes = await window.loadCustomEmotes();
+ emotes.forEach(emote => {
+ const div = document.createElement('div');
+ div.className = 'emoji-item rounded p-1 text-center';
+ div.style.cursor = 'pointer';
+ div.innerHTML = ``;
+ div.onclick = () => {
+ callback(emote.code);
+ if (!options.keepOpen) this.hide();
+ };
+ grid.appendChild(div);
+ });
+ } else {
+ const list = searchTerm ? ALL_EMOJIS.filter(e => e.includes(searchTerm)) : EMOJI_CATEGORIES[category];
+ (list || []).forEach(emoji => {
+ const div = document.createElement('div');
+ div.className = 'emoji-item rounded p-1 text-center';
+ div.style.cursor = 'pointer';
+ div.style.fontSize = '20px';
+ div.textContent = emoji;
+ div.onclick = () => {
+ callback(emoji);
+ if (!options.keepOpen) this.hide();
+ };
+ grid.appendChild(div);
+ });
+ }
+ };
+
+ categories.forEach(cat => {
+ const btn = document.createElement('button');
+ btn.className = 'btn btn-link text-white text-decoration-none p-2 mb-1 opacity-75';
+ btn.title = cat;
+ btn.innerHTML = categoryIcons[cat] || '😀';
+ btn.onclick = async () => {
+ tabs.querySelectorAll('button').forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
await renderGrid(cat);
};
tabs.appendChild(btn);
@@ -191,21 +306,20 @@ document.addEventListener('DOMContentLoaded', () => {
searchInput.oninput = async () => {
const term = searchInput.value.trim();
if (term) {
- tabs.querySelectorAll('button').forEach(b => {
- b.classList.remove('active');
- b.style.backgroundColor = 'transparent';
- });
+ tabs.querySelectorAll('button').forEach(b => b.classList.remove('active'));
await renderGrid(null, term);
} else {
const activeBtn = tabs.querySelector('button.active');
- const activeCat = activeBtn ? activeBtn.title : 'Custom';
- await renderGrid(activeCat);
+ await renderGrid(activeBtn ? activeBtn.title : 'Custom');
}
};
- picker.appendChild(tabs);
- picker.appendChild(searchContainer);
- picker.appendChild(grid);
+ mainLayout.appendChild(tabs);
+ contentArea.appendChild(searchContainer);
+ contentArea.appendChild(grid);
+ mainLayout.appendChild(contentArea);
+ picker.appendChild(mainLayout);
+
document.body.appendChild(picker);
this.currentPicker = picker;
@@ -213,24 +327,14 @@ document.addEventListener('DOMContentLoaded', () => {
const rect = anchor.getBoundingClientRect();
let top = rect.top - picker.offsetHeight - 10;
if (top < 0) top = rect.bottom + 10;
-
let left = rect.left;
if (left + picker.offsetWidth > window.innerWidth) left = window.innerWidth - picker.offsetWidth - 20;
if (left < 10) left = 10;
-
- // Ensure it doesn't go off screen at the bottom
- if (top + picker.offsetHeight > window.innerHeight) {
- top = window.innerHeight - picker.offsetHeight - 10;
- }
- // Ensure it doesn't go off screen at the top
- if (top < 0) top = 10;
-
picker.style.top = `${top}px`;
picker.style.left = `${left}px`;
await renderGrid('Custom');
- // Handle outside click
const outsideClick = (e) => {
if (!picker.contains(e.target) && e.target !== anchor && !anchor.contains(e.target)) {
this.hide();
@@ -239,7 +343,6 @@ document.addEventListener('DOMContentLoaded', () => {
};
setTimeout(() => document.addEventListener('click', outsideClick), 10);
},
-
hide() {
if (this.currentPicker) {
this.currentPicker.remove();
@@ -329,6 +432,13 @@ document.addEventListener('DOMContentLoaded', () => {
window.loadCustomEmotes();
+ const emotesTabBtn = document.getElementById('emotes-tab-btn');
+ if (emotesTabBtn) {
+ emotesTabBtn.addEventListener('click', () => {
+ setupSettingsEmotes();
+ });
+ }
+
// Scroll to bottom
scrollToBottom(true);
@@ -2177,6 +2287,8 @@ document.addEventListener('DOMContentLoaded', () => {
});
// User Settings - Avatar Search
+ // User Settings - Save logic removed and moved to index.php for reliability
+
const avatarSearchBtn = document.getElementById('search-avatar-btn');
const avatarSearchQuery = document.getElementById('avatar-search-query');
const avatarResults = document.getElementById('avatar-results');
@@ -2227,44 +2339,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
- // User Settings - Save
- const saveSettingsBtn = document.getElementById('save-settings-btn');
- saveSettingsBtn?.addEventListener('click', async () => {
- const form = document.getElementById('user-settings-form');
- if (!form) return;
-
- const formData = new FormData(form);
-
- // Ensure switches are correctly sent as 1/0
- const dndMode = document.getElementById('dnd-switch')?.checked ? '1' : '0';
- const soundNotifications = document.getElementById('sound-switch')?.checked ? '1' : '0';
- formData.set('dnd_mode', dndMode);
- formData.set('sound_notifications', soundNotifications);
-
- // Explicitly get theme to ensure it's captured
- const themeInput = form.querySelector('input[name="theme"]:checked');
- const theme = themeInput ? themeInput.value : 'dark';
- formData.set('theme', theme);
-
- // Visual feedback
- document.body.setAttribute('data-theme', theme);
-
- try {
- const resp = await fetch('api_v1_user.php?v=' + Date.now(), {
- method: 'POST',
- body: formData
- });
- const result = await resp.json();
- if (result.success) {
- window.location.href = window.location.pathname + window.location.search;
- } else {
- alert(result.error || 'Failed to save settings');
- }
- } catch (e) {
- console.error('Error saving settings:', e);
- alert('Failed to save settings. Please check your connection.');
- }
- });
+ // User Settings - Save handled in index.php
function escapeHTML(str) {
const div = document.createElement('div');
diff --git a/index.php b/index.php
index 75495d1..38dde4d 100644
--- a/index.php
+++ b/index.php
@@ -1059,12 +1059,62 @@ $emote_html = '
-
+
+
+