diff --git a/api_v1_channels.php b/api_v1_channels.php
index 4affb2a..43d7054 100644
--- a/api_v1_channels.php
+++ b/api_v1_channels.php
@@ -26,6 +26,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$orders = $json['orders'] ?? []; // Array of {id, position, category_id}
$user_id = $_SESSION['user_id'];
+ // Debug log
+ file_put_contents('debug_reorder.log', date('Y-m-d H:i:s') . " - Server: $server_id - Orders: " . json_encode($orders) . "\n", FILE_APPEND);
+
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) {
@@ -61,7 +64,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($chan && Permissions::hasPermission($user_id, $chan['server_id'], Permissions::MANAGE_CHANNELS)) {
if ($type === 'separator' && !$name) $name = 'separator';
- $name = strtolower(preg_replace('/[^a-zA-Z0-9\-]/', '-', $name));
+ // Allow spaces, accents and mixed case
+ $name = trim($name);
+ // Explicitly exclude position from update to prevent jumping to bottom
$stmt = db()->prepare("UPDATE channels SET name = ?, type = ?, status = ?, allow_file_sharing = ?, message_limit = ?, icon = ?, category_id = ? WHERE id = ?");
$stmt->execute([$name, $type, $status, $allow_file_sharing, $message_limit, $icon, $category_id, $channel_id]);
}
@@ -91,16 +96,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS) && ($name || $type === 'separator')) {
try {
if ($type === 'separator' && !$name) $name = 'separator';
- // Basic sanitization for channel name
- $name = strtolower(preg_replace('/[^a-zA-Z0-9\-]/', '-', $name));
+ // Allow spaces, accents and mixed case
+ $name = trim($name);
$allow_file_sharing = isset($_POST['allow_file_sharing']) ? 1 : 0;
$message_limit = !empty($_POST['message_limit']) ? (int)$_POST['message_limit'] : 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, message_limit, icon, category_id) VALUES (?, ?, ?, ?, ?, ?, ?)");
- $stmt->execute([$server_id, $name, $type, $allow_file_sharing, $message_limit, $icon, $category_id]);
+ // Get next position
+ $stmtPos = db()->prepare("SELECT MAX(position) as max_pos FROM channels WHERE server_id = ?");
+ $stmtPos->execute([$server_id]);
+ $maxPos = $stmtPos->fetch();
+ $nextPos = ($maxPos['max_pos'] ?? -1) + 1;
+
+ $stmt = db()->prepare("INSERT INTO channels (server_id, name, type, allow_file_sharing, message_limit, icon, category_id, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
+ $stmt->execute([$server_id, $name, $type, $allow_file_sharing, $message_limit, $icon, $category_id, $nextPos]);
$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 00cafb0..ab4a68b 100644
--- a/assets/css/discord.css
+++ b/assets/css/discord.css
@@ -134,7 +134,9 @@ body {
/* Channels Sidebar */
.channels-sidebar {
- width: 240px;
+ width: auto;
+ min-width: 240px;
+ max-width: 400px;
background-color: var(--bg-channels);
display: flex;
flex-direction: column;
@@ -166,6 +168,7 @@ body {
gap: 8px;
margin-bottom: 2px;
text-decoration: none;
+ white-space: nowrap;
}
.channel-item:hover {
@@ -189,7 +192,7 @@ body {
.channel-category {
color: var(--text-muted);
- font-size: 0.75em;
+ font-size: 0.85em;
text-transform: uppercase;
font-weight: bold;
margin-bottom: 4px;
@@ -197,6 +200,7 @@ body {
display: flex;
justify-content: space-between;
align-items: center;
+ white-space: nowrap;
}
.channel-category .channel-settings-btn,
diff --git a/debug_reorder.log b/debug_reorder.log
new file mode 100644
index 0000000..7038d2e
--- /dev/null
+++ b/debug_reorder.log
@@ -0,0 +1,4 @@
+2026-02-15 23:32:05 - Server: 1 - Orders: [{"id":"1","position":0,"category_id":null},{"id":"6","position":1,"category_id":null},{"id":"10","position":2,"category_id":null},{"id":"2","position":3,"category_id":null},{"id":"9","position":4,"category_id":null},{"id":"3","position":5,"category_id":null}]
+2026-02-15 23:35:48 - Server: 1 - Orders: [{"id":"10","position":0,"category_id":null},{"id":"1","position":1,"category_id":null},{"id":"6","position":2,"category_id":null},{"id":"2","position":3,"category_id":null},{"id":"9","position":4,"category_id":null},{"id":"3","position":5,"category_id":null}]
+2026-02-15 23:36:25 - Server: 1 - Orders: [{"id":"10","position":0,"category_id":null},{"id":"1","position":1,"category_id":"10"},{"id":"6","position":2,"category_id":"10"},{"id":"2","position":3,"category_id":null},{"id":"9","position":4,"category_id":null},{"id":"3","position":5,"category_id":null}]
+2026-02-15 23:36:28 - Server: 1 - Orders: [{"id":"10","position":0,"category_id":null},{"id":"1","position":1,"category_id":"10"},{"id":"6","position":2,"category_id":"10"},{"id":"2","position":3,"category_id":"10"},{"id":"9","position":4,"category_id":null},{"id":"3","position":5,"category_id":null}]
diff --git a/index.php b/index.php
index 683c6a8..b1fba61 100644
--- a/index.php
+++ b/index.php
@@ -1,4 +1,6 @@
"
data-category="">
@@ -401,47 +397,55 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
-
-
-
-
@@ -458,7 +462,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
@@ -1543,24 +1547,34 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
function updatePrefix(typeSelect, iconSelect, prefixSpan) {
if (!prefixSpan || !typeSelect) return;
- // Handle name input visibility for separator
+ // Handle name input visibility for separator and category
const modal = typeSelect.closest('.modal');
const nameInputContainer = modal.querySelector('input[name="name"]')?.closest('.mb-3');
const iconSelectContainer = modal.querySelector('select[name="icon"]')?.closest('.mb-3');
const fileSharingContainer = modal.querySelector('input[name="allow_file_sharing"]')?.closest('.mb-3') || modal.querySelector('input[name="allow_file_sharing"]')?.closest('.form-check');
const limitContainer = modal.querySelector('input[name="message_limit"]')?.closest('.mb-3');
+ const categoryContainer = modal.querySelector('select[name="category_id"]')?.closest('.mb-3');
if (typeSelect.value === 'separator') {
if (nameInputContainer) nameInputContainer.style.display = 'none';
if (iconSelectContainer) iconSelectContainer.style.display = 'none';
if (fileSharingContainer) fileSharingContainer.style.display = 'none';
if (limitContainer) limitContainer.style.display = 'none';
+ if (categoryContainer) categoryContainer.style.display = 'none';
if (modal.querySelector('input[name="name"]')) modal.querySelector('input[name="name"]').required = false;
+ } else if (typeSelect.value === 'category') {
+ if (nameInputContainer) nameInputContainer.style.display = 'block';
+ if (iconSelectContainer) iconSelectContainer.style.display = 'block';
+ if (fileSharingContainer) fileSharingContainer.style.display = 'none';
+ if (limitContainer) limitContainer.style.display = 'none';
+ if (categoryContainer) categoryContainer.style.display = 'none';
+ if (modal.querySelector('input[name="name"]')) modal.querySelector('input[name="name"]').required = true;
} else {
if (nameInputContainer) nameInputContainer.style.display = 'block';
if (iconSelectContainer) iconSelectContainer.style.display = 'block';
if (fileSharingContainer) fileSharingContainer.style.display = 'block';
if (limitContainer) limitContainer.style.display = 'block';
+ if (categoryContainer) categoryContainer.style.display = 'block';
if (modal.querySelector('input[name="name"]')) modal.querySelector('input[name="name"]').required = true;
}
@@ -1649,6 +1663,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
groups.forEach(group => {
new Sortable(group, {
group: 'channels',
+ draggable: '.channel-item-container',
animation: 150,
ghostClass: 'sortable-ghost',
onEnd: function() {
@@ -1660,6 +1675,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
// Sortable for categories themselves and top-level channels
const sidebar = document.getElementById('sidebar-channels-list');
new Sortable(sidebar, {
+ group: 'channels',
animation: 150,
draggable: '.category-wrapper, .channel-item-container:not(.category-group .channel-item-container)',
ghostClass: 'sortable-ghost',
@@ -1679,11 +1695,13 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
const topLevelItems = sidebar.children;
Array.from(topLevelItems).forEach(item => {
+ const itemId = item.dataset.id;
+ if (!itemId) return;
+
if (item.classList.contains('category-wrapper')) {
// It's a category
- const catId = item.dataset.id;
orders.push({
- id: catId,
+ id: itemId,
position: position++,
category_id: null
});
@@ -1691,16 +1709,18 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
// Now add all channels inside this category
const subChannels = item.querySelectorAll('.category-group .channel-item-container');
subChannels.forEach(sub => {
- orders.push({
- id: sub.dataset.id,
- position: position++,
- category_id: catId
- });
+ if (sub.dataset.id) {
+ orders.push({
+ id: sub.dataset.id,
+ position: position++,
+ category_id: itemId
+ });
+ }
});
} else if (item.classList.contains('channel-item-container')) {
- // It's a top level channel
+ // It's a top level channel or separator
orders.push({
- id: item.dataset.id,
+ id: itemId,
position: position++,
category_id: null
});
@@ -1713,7 +1733,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'reorder',
- server_id: ,
+ server_id: "",
orders: orders
})
});
diff --git a/requests.log b/requests.log
new file mode 100644
index 0000000..044ac28
--- /dev/null
+++ b/requests.log
@@ -0,0 +1,12 @@
+2026-02-15 23:22:04 - GET /?fl_project=38443 - POST: []
+2026-02-15 23:27:59 - GET / - POST: []
+2026-02-15 23:28:03 - HEAD / - POST: []
+2026-02-15 23:28:16 - GET /?fl_project=38443 - POST: []
+2026-02-15 23:31:59 - GET /index.php?server_id=1&channel_id=10 - POST: []
+2026-02-15 23:32:07 - GET /index.php?server_id=1&channel_id=10 - POST: []
+2026-02-15 23:32:12 - GET /index.php?server_id=1&channel_id=10 - POST: []
+2026-02-15 23:36:00 - GET /index.php?server_id=1&channel_id=10 - POST: []
+2026-02-15 23:36:17 - GET /index.php?server_id=1&channel_id=1 - POST: []
+2026-02-15 23:36:35 - GET /index.php?server_id=1&channel_id=2 - POST: []
+2026-02-15 23:38:20 - GET /index.php?server_id=1&channel_id=6 - POST: []
+2026-02-15 23:38:22 - GET /index.php?server_id=1&channel_id=1 - POST: []
diff --git a/test_reorder.php b/test_reorder.php
new file mode 100644
index 0000000..828c8d6
--- /dev/null
+++ b/test_reorder.php
@@ -0,0 +1,17 @@
+ 1, 'position' => 1, 'category_id' => null],
+ ['id' => 10, 'position' => 0, 'category_id' => null],
+ ['id' => 6, 'position' => 2, 'category_id' => null],
+ ['id' => 2, 'position' => 3, 'category_id' => null],
+ ['id' => 9, 'position' => 4, 'category_id' => null],
+ ['id' => 3, 'position' => 5, 'category_id' => null]
+];
+$server_id = 1;
+$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'], $o['id'], $server_id]);
+ echo "Updated ID {$o['id']} to position {$o['position']}\n";
+}
+?>