diff --git a/api_v1_channels.php b/api_v1_channels.php index 62b2dbf..cadc6d3 100644 --- a/api_v1_channels.php +++ b/api_v1_channels.php @@ -17,6 +17,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { } if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Handle JSON input + $json = json_decode(file_get_contents('php://input'), true); + if ($json) { + $action = $json['action'] ?? ''; + if ($action === 'reorder') { + $server_id = $json['server_id'] ?? 0; + $orders = $json['orders'] ?? []; // Array of {id, position, category_id} + $user_id = $_SESSION['user_id']; + + if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS)) { + $stmt = db()->prepare("UPDATE channels SET position = ?, category_id = ? WHERE id = ? AND server_id = ?"); + foreach ($orders as $o) { + $stmt->execute([$o['position'], $o['category_id'] ?: null, $o['id'], $server_id]); + } + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Permission denied']); + } + exit; + } + } + $action = $_POST['action'] ?? 'create'; $server_id = $_POST['server_id'] ?? 0; $user_id = $_SESSION['user_id']; @@ -30,6 +52,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $message_limit = !empty($_POST['message_limit']) ? (int)$_POST['message_limit'] : null; $theme_color = $_POST['theme_color'] ?? null; if ($theme_color === '') $theme_color = null; + $icon = $_POST['icon'] ?? null; + if ($icon === '') $icon = null; + $category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null; // Check if user has permission to manage channels $stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?"); @@ -38,8 +63,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($chan && Permissions::hasPermission($user_id, $chan['server_id'], Permissions::MANAGE_CHANNELS)) { $name = strtolower(preg_replace('/[^a-zA-Z0-9\-]/', '-', $name)); - $stmt = db()->prepare("UPDATE channels SET name = ?, type = ?, status = ?, allow_file_sharing = ?, theme_color = ?, message_limit = ? WHERE id = ?"); - $stmt->execute([$name, $type, $status, $allow_file_sharing, $theme_color, $message_limit, $channel_id]); + $stmt = db()->prepare("UPDATE channels SET name = ?, type = ?, status = ?, allow_file_sharing = ?, theme_color = ?, message_limit = ?, icon = ?, category_id = ? WHERE id = ?"); + $stmt->execute([$name, $type, $status, $allow_file_sharing, $theme_color, $message_limit, $icon, $category_id, $channel_id]); } header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id); exit; @@ -72,9 +97,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $message_limit = !empty($_POST['message_limit']) ? (int)$_POST['message_limit'] : null; $theme_color = $_POST['theme_color'] ?? null; if ($theme_color === '') $theme_color = null; + $icon = $_POST['icon'] ?? null; + if ($icon === '') $icon = null; + $category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null; - $stmt = db()->prepare("INSERT INTO channels (server_id, name, type, allow_file_sharing, theme_color, message_limit) VALUES (?, ?, ?, ?, ?, ?)"); - $stmt->execute([$server_id, $name, $type, $allow_file_sharing, $theme_color, $message_limit]); + $stmt = db()->prepare("INSERT INTO channels (server_id, name, type, allow_file_sharing, theme_color, message_limit, icon, category_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$server_id, $name, $type, $allow_file_sharing, $theme_color, $message_limit, $icon, $category_id]); $channel_id = db()->lastInsertId(); header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id); diff --git a/assets/css/discord.css b/assets/css/discord.css index f3498b6..13dcd35 100644 --- a/assets/css/discord.css +++ b/assets/css/discord.css @@ -178,17 +178,6 @@ body { color: var(--text-primary); } -.channel-item::before { - content: "#"; - font-size: 1.2em; - font-weight: 300; -} - -.voice-item::before { - content: "๐Ÿ”Š"; - font-size: 0.9em; -} - .server-icon.add-btn { color: #23a559; } @@ -203,13 +192,48 @@ body { font-size: 0.75em; text-transform: uppercase; font-weight: bold; - margin-bottom: 8px; - padding-left: 8px; + margin-bottom: 4px; + padding: 16px 8px 4px 8px; display: flex; justify-content: space-between; align-items: center; } +.channel-category .channel-settings-btn, +.channel-category .add-channel-btn { + opacity: 0; + transition: opacity 0.2s; + font-size: 1.2em; + cursor: pointer; +} + +.channel-category:hover .channel-settings-btn, +.channel-category:hover .add-channel-btn { + opacity: 1; +} + +.channel-item-container .channel-settings-btn { + opacity: 0; + transition: opacity 0.2s; +} + +.channel-item-container:hover .channel-settings-btn { + opacity: 1; +} + +.category-group .channel-item-container { + margin-left: 8px; +} + +.category-group { + min-height: 5px; +} + +.sortable-ghost { + background-color: var(--hover) !important; + opacity: 0.5; +} + .add-channel-btn { cursor: pointer; font-size: 1.2em; @@ -400,7 +424,27 @@ body { width: 240px; background-color: var(--bg-members); padding: 24px 8px; - display: none; /* Hidden on mobile/small screens */ + display: flex; + flex-direction: column; +} + +.members-sidebar.hidden { + display: none; +} + +@media (max-width: 992px) { + .members-sidebar { + display: none; + } + .members-sidebar.show { + display: flex; + position: absolute; + right: 0; + top: 48px; + bottom: 0; + z-index: 100; + box-shadow: -2px 0 10px rgba(0,0,0,0.5); + } } /* Reactions */ diff --git a/assets/js/main.js b/assets/js/main.js index 0b16feb..f05e60f 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -53,18 +53,7 @@ document.addEventListener('DOMContentLoaded', () => { new Notification(`Mention in #${window.currentChannelName}`, { body: `${data.username}: ${data.content}`, icon: data.avatar_url || '' - if (e.target.classList.contains('move-rule-btn')) { - const id = e.target.dataset.id; - const dir = e.target.dataset.dir; - const resp = await fetch('api_v1_rules.php', { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id, dir }) - }); - if ((await resp.json()).success) location.reload(); - } - }); - + }); } } } @@ -594,10 +583,21 @@ document.addEventListener('DOMContentLoaded', () => { } }); - document.addEventListener('click', (e) => { + document.addEventListener('click', async (e) => { if (!e.target.closest('.search-container')) { searchResults.style.display = 'none'; } + + if (e.target.classList.contains('move-rule-btn')) { + const id = e.target.dataset.id; + const dir = e.target.dataset.dir; + const resp = await fetch('api_v1_rules.php', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id, dir }) + }); + if ((await resp.json()).success) location.reload(); + } }); // Roles Management @@ -615,6 +615,7 @@ document.addEventListener('DOMContentLoaded', () => { modal.querySelector('#edit-channel-limit').value = btn.dataset.limit || ''; modal.querySelector('#edit-channel-status').value = btn.dataset.status || ''; modal.querySelector('#edit-channel-theme').value = btn.dataset.theme || '#5865f2'; + modal.querySelector('#edit-channel-icon').value = btn.dataset.icon || ''; modal.querySelector('#delete-channel-id').value = channelId; // Show/Hide RSS tab @@ -1348,6 +1349,19 @@ document.addEventListener('DOMContentLoaded', () => { } catch (e) { console.error(e); } }); + // Toggle members sidebar + const toggleMembersBtn = document.getElementById('toggle-members-btn'); + const membersSidebar = document.querySelector('.members-sidebar'); + if (toggleMembersBtn && membersSidebar) { + toggleMembersBtn.addEventListener('click', () => { + if (window.innerWidth > 992) { + membersSidebar.classList.toggle('hidden'); + } else { + membersSidebar.classList.toggle('show'); + } + }); + } + // User Settings - Save const saveSettingsBtn = document.getElementById('save-settings-btn'); saveSettingsBtn?.addEventListener('click', async () => { diff --git a/assets/pasted-20260215-151928-c94822be.png b/assets/pasted-20260215-151928-c94822be.png new file mode 100644 index 0000000..c6b60fa Binary files /dev/null and b/assets/pasted-20260215-151928-c94822be.png differ diff --git a/assets/pasted-20260215-153522-763a8478.png b/assets/pasted-20260215-153522-763a8478.png new file mode 100644 index 0000000..1acd0a1 Binary files /dev/null and b/assets/pasted-20260215-153522-763a8478.png differ diff --git a/includes/permissions.php b/includes/permissions.php index 9e467f5..064c9e9 100644 --- a/includes/permissions.php +++ b/includes/permissions.php @@ -9,6 +9,11 @@ class Permissions { const ADMINISTRATOR = 32; public static function hasPermission($user_id, $server_id, $permission) { + $stmt = db()->prepare("SELECT is_admin FROM users WHERE id = ?"); + $stmt->execute([$user_id]); + $user = $stmt->fetch(); + if ($user && $user['is_admin']) return true; + $stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server = $stmt->fetch(); diff --git a/index.php b/index.php index ca8469c..cd80336 100644 --- a/index.php +++ b/index.php @@ -63,7 +63,7 @@ if ($is_dm_view) { $active_server_id = $_GET['server_id'] ?? ($servers[0]['id'] ?? 1); // Fetch channels - $stmt = db()->prepare("SELECT * FROM channels WHERE server_id = ?"); + $stmt = db()->prepare("SELECT * FROM channels WHERE server_id = ? ORDER BY position ASC, id ASC"); $stmt->execute([$active_server_id]); $channels = $stmt->fetchAll(); $active_channel_id = $_GET['channel_id'] ?? ($channels[0]['id'] ?? 1); @@ -192,8 +192,10 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; + +