diff --git a/api_v1_messages.php b/api_v1_messages.php index b24c250..8081a21 100644 --- a/api_v1_messages.php +++ b/api_v1_messages.php @@ -65,6 +65,37 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { } exit; } + + if (isset($_GET['after_id'])) { + try { + $after_id = (int)$_GET['after_id']; + // Get server_id for the channel + $stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?"); + $stmt->execute([$channel_id]); + $server_id = $stmt->fetchColumn(); + + $stmt = db()->prepare(" + SELECT m.*, u.username, u.avatar_url, + (SELECT r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color + FROM messages m + JOIN users u ON m.user_id = u.id + WHERE m.channel_id = ? AND m.id > ? + ORDER BY m.id ASC + "); + $stmt->execute([$server_id ?: 0, $channel_id, $after_id]); + $msgs = $stmt->fetchAll(); + + foreach ($msgs as &$m) { + $m['time'] = date('H:i', strtotime($m['created_at'])); + $m['metadata'] = $m['metadata'] ? json_decode($m['metadata']) : null; + } + + echo json_encode(['success' => true, 'messages' => $msgs]); + } catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + exit; + } } if ($_SERVER['REQUEST_METHOD'] === 'PUT') { @@ -213,11 +244,11 @@ try { SELECT id FROM messages WHERE channel_id = ? ORDER BY created_at DESC, id DESC - LIMIT ? + LIMIT " . $limit . " ) as tmp ) "); - $stmt->execute([$channel_id, $channel_id, $limit]); + $stmt->execute([$channel_id, $channel_id]); } // Get server_id for the channel diff --git a/assets/css/discord.css b/assets/css/discord.css index 13dcd35..580d42d 100644 --- a/assets/css/discord.css +++ b/assets/css/discord.css @@ -382,7 +382,13 @@ body { .message-author { font-weight: bold; font-size: 0.95em; - margin-bottom: 4px; +} + +.message-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 2px; } .message-text { diff --git a/assets/js/main.js b/assets/js/main.js index f05e60f..1bec195 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -86,6 +86,29 @@ document.addEventListener('DOMContentLoaded', () => { } connectWS(); + // Polling as fallback for real-time + let lastMessageId = 0; + const findLastMessageId = () => { + const items = document.querySelectorAll('.message-item'); + if (items.length > 0) { + lastMessageId = Math.max(...Array.from(items).map(i => parseInt(i.dataset.id) || 0)); + } + }; + findLastMessageId(); + + setInterval(async () => { + if (!currentChannel) return; + try { + const resp = await fetch(`api_v1_messages.php?channel_id=${currentChannel}&after_id=${lastMessageId}`); + const data = await resp.json(); + if (data.success && data.messages && data.messages.length > 0) { + data.messages.forEach(msg => { + appendMessage(msg); + }); + } + } catch (e) { } + }, 1000); + function showTyping(username) { typingIndicator.textContent = `${username} is typing...`; clearTimeout(typingTimeout); @@ -150,6 +173,8 @@ document.addEventListener('DOMContentLoaded', () => { if (xhr.status === 200) { const result = JSON.parse(xhr.responseText); if (result.success) { + appendMessage(result.message); + messagesList.scrollTop = messagesList.scrollHeight; if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'message', @@ -158,9 +183,6 @@ document.addEventListener('DOMContentLoaded', () => { channel_id: currentChannel }) })); - } else { - appendMessage(result.message); - messagesList.scrollTop = messagesList.scrollHeight; } } else { alert(result.error || 'Failed to send message'); @@ -1384,7 +1406,6 @@ document.addEventListener('DOMContentLoaded', () => { alert(result.error || 'Failed to save settings'); } }); -}); function escapeHTML(str) { const div = document.createElement('div'); @@ -1393,10 +1414,17 @@ document.addEventListener('DOMContentLoaded', () => { } function appendMessage(msg) { + if (!msg || !msg.id) return; + if (document.querySelector(`.message-item[data-id="${msg.id}"]`)) return; + const messagesList = document.getElementById('messages-list'); const div = document.createElement('div'); div.className = 'message-item'; div.dataset.id = msg.id; + + if (parseInt(msg.id) > lastMessageId) { + lastMessageId = parseInt(msg.id); + } const avatarStyle = msg.avatar_url ? `background-image: url('${msg.avatar_url}');` : ''; let attachmentHtml = ''; @@ -1470,11 +1498,10 @@ document.addEventListener('DOMContentLoaded', () => { div.innerHTML = `
-
- ${escapeHTML(msg.username)} +
+ ${escapeHTML(msg.username)} ${msg.time} ${pinnedBadge} - ${actionsHtml}
${escapeHTML(msg.content).replace(/\n/g, '
').replace(mentionRegex, `@${window.currentUsername}`)} @@ -1486,6 +1513,9 @@ document.addEventListener('DOMContentLoaded', () => { +
+ ${actionsHtml} `; messagesList.appendChild(div); + messagesList.scrollTop = messagesList.scrollHeight; } +}); diff --git a/assets/pasted-20260215-162151-f0d79b58.png b/assets/pasted-20260215-162151-f0d79b58.png new file mode 100644 index 0000000..67fce27 Binary files /dev/null and b/assets/pasted-20260215-162151-f0d79b58.png differ diff --git a/index.php b/index.php index 2025141..a16ffe4 100644 --- a/index.php +++ b/index.php @@ -602,8 +602,10 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
">
-
"> - +
+ "> + + @@ -611,21 +613,6 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; Pinned - -
- - - - - - - - - - - -
-
+
+ +
+ + + + + + + + + + + +
+