categories

This commit is contained in:
Flatlogic Bot 2026-02-15 23:38:38 +00:00
parent f604713529
commit b5ae307f55
6 changed files with 132 additions and 64 deletions

View File

@ -26,6 +26,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$orders = $json['orders'] ?? []; // Array of {id, position, category_id} $orders = $json['orders'] ?? []; // Array of {id, position, category_id}
$user_id = $_SESSION['user_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)) { if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS)) {
$stmt = db()->prepare("UPDATE channels SET position = ?, category_id = ? WHERE id = ? AND server_id = ?"); $stmt = db()->prepare("UPDATE channels SET position = ?, category_id = ? WHERE id = ? AND server_id = ?");
foreach ($orders as $o) { 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 ($chan && Permissions::hasPermission($user_id, $chan['server_id'], Permissions::MANAGE_CHANNELS)) {
if ($type === 'separator' && !$name) $name = 'separator'; 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 = 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]); $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')) { if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS) && ($name || $type === 'separator')) {
try { try {
if ($type === 'separator' && !$name) $name = 'separator'; if ($type === 'separator' && !$name) $name = 'separator';
// Basic sanitization for channel name // Allow spaces, accents and mixed case
$name = strtolower(preg_replace('/[^a-zA-Z0-9\-]/', '-', $name)); $name = trim($name);
$allow_file_sharing = isset($_POST['allow_file_sharing']) ? 1 : 0; $allow_file_sharing = isset($_POST['allow_file_sharing']) ? 1 : 0;
$message_limit = !empty($_POST['message_limit']) ? (int)$_POST['message_limit'] : null; $message_limit = !empty($_POST['message_limit']) ? (int)$_POST['message_limit'] : null;
$icon = $_POST['icon'] ?? null; $icon = $_POST['icon'] ?? null;
if ($icon === '') $icon = null; if ($icon === '') $icon = null;
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : 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 (?, ?, ?, ?, ?, ?, ?)"); // Get next position
$stmt->execute([$server_id, $name, $type, $allow_file_sharing, $message_limit, $icon, $category_id]); $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(); $channel_id = db()->lastInsertId();
header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id); header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id);

View File

@ -134,7 +134,9 @@ body {
/* Channels Sidebar */ /* Channels Sidebar */
.channels-sidebar { .channels-sidebar {
width: 240px; width: auto;
min-width: 240px;
max-width: 400px;
background-color: var(--bg-channels); background-color: var(--bg-channels);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -166,6 +168,7 @@ body {
gap: 8px; gap: 8px;
margin-bottom: 2px; margin-bottom: 2px;
text-decoration: none; text-decoration: none;
white-space: nowrap;
} }
.channel-item:hover { .channel-item:hover {
@ -189,7 +192,7 @@ body {
.channel-category { .channel-category {
color: var(--text-muted); color: var(--text-muted);
font-size: 0.75em; font-size: 0.85em;
text-transform: uppercase; text-transform: uppercase;
font-weight: bold; font-weight: bold;
margin-bottom: 4px; margin-bottom: 4px;
@ -197,6 +200,7 @@ body {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
white-space: nowrap;
} }
.channel-category .channel-settings-btn, .channel-category .channel-settings-btn,

4
debug_reorder.log Normal file
View File

@ -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}]

134
index.php
View File

@ -1,4 +1,6 @@
<?php <?php
// Request log
file_put_contents('requests.log', date('Y-m-d H:i:s') . " - " . $_SERVER['REQUEST_METHOD'] . " " . $_SERVER['REQUEST_URI'] . " - POST: " . json_encode($_POST) . "\n", FILE_APPEND);
require_once 'auth/session.php'; require_once 'auth/session.php';
function renderRoleIcon($icon, $size = '12px') { function renderRoleIcon($icon, $size = '12px') {
@ -330,12 +332,6 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<?php endforeach; ?> <?php endforeach; ?>
<?php else: ?> <?php else: ?>
<?php <?php
// Separate categories and channels
$categories = array_filter($channels, function($c) { return $c['type'] === 'category'; });
$top_level_channels = array_filter($channels, function($c) use ($channels) {
return $c['type'] !== 'category' && (empty($c['category_id']) || !in_array($c['category_id'], array_column($channels, 'id')));
});
// Helper to render a channel item // Helper to render a channel item
function renderChannelItem($c, $active_channel_id, $active_server_id, $can_manage_channels) { function renderChannelItem($c, $active_channel_id, $active_server_id, $can_manage_channels) {
if ($c['type'] === 'separator') { if ($c['type'] === 'separator') {
@ -351,7 +347,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
data-files="0" data-files="0"
data-limit="0" data-limit="0"
data-status="" data-status=""
data-icon="" data-icon="<?php echo htmlspecialchars($c['icon'] ?? ''); ?>"
data-category="<?php echo $c['category_id'] ?? ''; ?>"> data-category="<?php echo $c['category_id'] ?? ''; ?>">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82 1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82 1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</span> </span>
@ -401,47 +397,55 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<?php <?php
} }
// Render top level channels $category_ids = array_column(array_filter($channels, function($c) { return $c['type'] === 'category'; }), 'id');
if (!empty($top_level_channels)) {
foreach($top_level_channels as $c) {
renderChannelItem($c, $active_channel_id, $active_server_id, $can_manage_channels);
}
}
// Render categories and their channels foreach($channels as $item) {
foreach($categories as $cat) { // Skip channels that have a parent category (they will be rendered inside their category)
?> if ($item['type'] !== 'category' && !empty($item['category_id']) && in_array($item['category_id'], $category_ids)) {
<div class="category-wrapper" data-id="<?php echo $cat['id']; ?>"> continue;
<div class="channel-category d-flex align-items-center mt-3" data-id="<?php echo $cat['id']; ?>"> }
<span class="category-name flex-grow-1 text-uppercase fw-bold" style="font-size: 0.7em; cursor: pointer; color: var(--text-muted);"><?php echo htmlspecialchars($cat['name']); ?></span>
<?php if ($can_manage_channels): ?> if ($item['type'] === 'category') {
<span class="channel-settings-btn ms-1" style="cursor: pointer; color: var(--text-muted);" // Render category and its children
data-bs-toggle="modal" data-bs-target="#editChannelModal" ?>
data-id="<?php echo $cat['id']; ?>" <div class="category-wrapper" data-id="<?php echo $item['id']; ?>">
data-name="<?php echo htmlspecialchars($cat['name']); ?>" <div class="channel-category d-flex align-items-center mt-3" data-id="<?php echo $item['id']; ?>">
data-type="category" <?php if (!empty($item['icon'])): ?>
data-files="0" <span class="me-1" style="font-size: 14px;"><?php echo renderRoleIcon($item['icon'], '14px'); ?></span>
data-limit="0" <?php endif; ?>
data-status="" <span class="category-name flex-grow-1 text-uppercase fw-bold" style="font-size: 0.85em; cursor: pointer; color: var(--text-muted);"><?php echo htmlspecialchars($item['name']); ?></span>
data-icon="" <?php if ($can_manage_channels): ?>
data-category="" <span class="channel-settings-btn ms-1" style="cursor: pointer; color: var(--text-muted);"
data-theme=""> data-bs-toggle="modal" data-bs-target="#editChannelModal"
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82 1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg> data-id="<?php echo $item['id']; ?>"
</span> data-name="<?php echo htmlspecialchars($item['name']); ?>"
<span class="add-channel-btn ms-1" title="Create Channel" data-bs-toggle="modal" data-bs-target="#addChannelModal" data-type="chat" data-category-id="<?php echo $cat['id']; ?>">+</span> data-type="category"
<?php endif; ?> data-files="0"
</div> data-limit="0"
<div class="category-group" data-category-id="<?php echo $cat['id']; ?>"> data-status=""
<?php data-icon="<?php echo htmlspecialchars($item['icon'] ?? ''); ?>"
foreach($channels as $c) { data-category=""
if ($c['type'] !== 'category' && $c['category_id'] == $cat['id']) { data-theme="">
renderChannelItem($c, $active_channel_id, $active_server_id, $can_manage_channels); <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82 1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</span>
<span class="add-channel-btn ms-1" title="Create Channel" data-bs-toggle="modal" data-bs-target="#addChannelModal" data-type="chat" data-category-id="<?php echo $item['id']; ?>">+</span>
<?php endif; ?>
</div>
<div class="category-group" data-category-id="<?php echo $item['id']; ?>">
<?php
foreach($channels as $c) {
if ($c['type'] !== 'category' && $c['category_id'] == $item['id']) {
renderChannelItem($c, $active_channel_id, $active_server_id, $can_manage_channels);
}
} }
} ?>
?> </div>
</div> </div>
</div> <?php
<?php } else {
// Render top level channel
renderChannelItem($item, $active_channel_id, $active_server_id, $can_manage_channels);
}
} }
?> ?>
<?php endif; ?> <?php endif; ?>
@ -458,7 +462,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
</div> </div>
<div class="user-actions"> <div class="user-actions">
<a href="#" title="Settings" style="color: var(--text-muted); margin-right: 8px;" data-bs-toggle="modal" data-bs-target="#userSettingsModal"> <a href="#" title="Settings" style="color: var(--text-muted); margin-right: 8px;" data-bs-toggle="modal" data-bs-target="#userSettingsModal">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82 1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</a> </a>
<a href="auth/logout.php" title="Logout" style="color: var(--text-muted);"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg></a> <a href="auth/logout.php" title="Logout" style="color: var(--text-muted);"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg></a>
</div> </div>
@ -1543,24 +1547,34 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
function updatePrefix(typeSelect, iconSelect, prefixSpan) { function updatePrefix(typeSelect, iconSelect, prefixSpan) {
if (!prefixSpan || !typeSelect) return; if (!prefixSpan || !typeSelect) return;
// Handle name input visibility for separator // Handle name input visibility for separator and category
const modal = typeSelect.closest('.modal'); const modal = typeSelect.closest('.modal');
const nameInputContainer = modal.querySelector('input[name="name"]')?.closest('.mb-3'); const nameInputContainer = modal.querySelector('input[name="name"]')?.closest('.mb-3');
const iconSelectContainer = modal.querySelector('select[name="icon"]')?.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 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 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 (typeSelect.value === 'separator') {
if (nameInputContainer) nameInputContainer.style.display = 'none'; if (nameInputContainer) nameInputContainer.style.display = 'none';
if (iconSelectContainer) iconSelectContainer.style.display = 'none'; if (iconSelectContainer) iconSelectContainer.style.display = 'none';
if (fileSharingContainer) fileSharingContainer.style.display = 'none'; if (fileSharingContainer) fileSharingContainer.style.display = 'none';
if (limitContainer) limitContainer.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; 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 { } else {
if (nameInputContainer) nameInputContainer.style.display = 'block'; if (nameInputContainer) nameInputContainer.style.display = 'block';
if (iconSelectContainer) iconSelectContainer.style.display = 'block'; if (iconSelectContainer) iconSelectContainer.style.display = 'block';
if (fileSharingContainer) fileSharingContainer.style.display = 'block'; if (fileSharingContainer) fileSharingContainer.style.display = 'block';
if (limitContainer) limitContainer.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; 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 => { groups.forEach(group => {
new Sortable(group, { new Sortable(group, {
group: 'channels', group: 'channels',
draggable: '.channel-item-container',
animation: 150, animation: 150,
ghostClass: 'sortable-ghost', ghostClass: 'sortable-ghost',
onEnd: function() { onEnd: function() {
@ -1660,6 +1675,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
// Sortable for categories themselves and top-level channels // Sortable for categories themselves and top-level channels
const sidebar = document.getElementById('sidebar-channels-list'); const sidebar = document.getElementById('sidebar-channels-list');
new Sortable(sidebar, { new Sortable(sidebar, {
group: 'channels',
animation: 150, animation: 150,
draggable: '.category-wrapper, .channel-item-container:not(.category-group .channel-item-container)', draggable: '.category-wrapper, .channel-item-container:not(.category-group .channel-item-container)',
ghostClass: 'sortable-ghost', ghostClass: 'sortable-ghost',
@ -1679,11 +1695,13 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
const topLevelItems = sidebar.children; const topLevelItems = sidebar.children;
Array.from(topLevelItems).forEach(item => { Array.from(topLevelItems).forEach(item => {
const itemId = item.dataset.id;
if (!itemId) return;
if (item.classList.contains('category-wrapper')) { if (item.classList.contains('category-wrapper')) {
// It's a category // It's a category
const catId = item.dataset.id;
orders.push({ orders.push({
id: catId, id: itemId,
position: position++, position: position++,
category_id: null category_id: null
}); });
@ -1691,16 +1709,18 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
// Now add all channels inside this category // Now add all channels inside this category
const subChannels = item.querySelectorAll('.category-group .channel-item-container'); const subChannels = item.querySelectorAll('.category-group .channel-item-container');
subChannels.forEach(sub => { subChannels.forEach(sub => {
orders.push({ if (sub.dataset.id) {
id: sub.dataset.id, orders.push({
position: position++, id: sub.dataset.id,
category_id: catId position: position++,
}); category_id: itemId
});
}
}); });
} else if (item.classList.contains('channel-item-container')) { } else if (item.classList.contains('channel-item-container')) {
// It's a top level channel // It's a top level channel or separator
orders.push({ orders.push({
id: item.dataset.id, id: itemId,
position: position++, position: position++,
category_id: null category_id: null
}); });
@ -1713,7 +1733,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
action: 'reorder', action: 'reorder',
server_id: <?php echo $active_server_id; ?>, server_id: "<?php echo $active_server_id; ?>",
orders: orders orders: orders
}) })
}); });

12
requests.log Normal file
View File

@ -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: []

17
test_reorder.php Normal file
View File

@ -0,0 +1,17 @@
<?php
require 'db/config.php';
$orders = [
['id' => 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";
}
?>