1819 lines
123 KiB
PHP
1819 lines
123 KiB
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';
|
||
|
||
function renderRoleIcon($icon, $size = '12px') {
|
||
if (empty($icon)) return '';
|
||
$isUrl = (strpos($icon, 'http') === 0 || strpos($icon, '/') === 0);
|
||
$isFa = (strpos($icon, 'fa-') === 0);
|
||
|
||
if ($isUrl) {
|
||
return '<img src="' . htmlspecialchars($icon) . '" class="role-icon ms-1" style="width: '.$size.'; height: '.$size.'; vertical-align: middle; object-fit: contain;">';
|
||
} elseif ($isFa) {
|
||
return '<i class="fa-solid ' . htmlspecialchars($icon) . ' ms-1" style="font-size: '.$size.'; vertical-align: middle;"></i>';
|
||
} else {
|
||
return '<span class="ms-1" style="font-size: '.$size.'; vertical-align: middle;">' . htmlspecialchars($icon) . '</span>';
|
||
}
|
||
}
|
||
requireLogin();
|
||
|
||
$user = getCurrentUser();
|
||
$current_user_id = $user['id'];
|
||
|
||
// Fetch servers user is member of
|
||
$stmt = db()->prepare("
|
||
SELECT s.* FROM servers s
|
||
JOIN server_members sm ON s.id = sm.server_id
|
||
WHERE sm.user_id = ?
|
||
LIMIT 20
|
||
");
|
||
$stmt->execute([$current_user_id]);
|
||
$servers = $stmt->fetchAll();
|
||
$is_dm_view = (isset($_GET['server_id']) && $_GET['server_id'] == 'dms') || !isset($_GET['server_id']) && empty($servers);
|
||
|
||
if ($is_dm_view) {
|
||
$active_server_id = 'dms';
|
||
// Fetch DM channels
|
||
$stmt = db()->prepare("
|
||
SELECT c.id, u.username as other_user, u.avatar_url, u.status, u.id as other_user_id
|
||
FROM channels c
|
||
JOIN channel_members cm1 ON c.id = cm1.channel_id
|
||
JOIN channel_members cm2 ON c.id = cm2.channel_id
|
||
JOIN users u ON cm2.user_id = u.id
|
||
WHERE c.type = 'dm' AND cm1.user_id = ? AND cm2.user_id != ?
|
||
");
|
||
$stmt->execute([$current_user_id, $current_user_id]);
|
||
$dm_channels = $stmt->fetchAll();
|
||
|
||
$active_channel_id = $_GET['channel_id'] ?? ($dm_channels[0]['id'] ?? 0);
|
||
$channel_theme = null; // DMs don't have custom themes for now
|
||
|
||
if ($active_channel_id) {
|
||
// Fetch DM messages
|
||
$stmt = db()->prepare("
|
||
SELECT m.*, u.username, u.avatar_url
|
||
FROM messages m
|
||
JOIN users u ON m.user_id = u.id
|
||
WHERE m.channel_id = ?
|
||
ORDER BY m.created_at ASC
|
||
LIMIT 50
|
||
");
|
||
$stmt->execute([$active_channel_id]);
|
||
$messages = $stmt->fetchAll();
|
||
|
||
$current_channel_name = 'Direct Message';
|
||
foreach($dm_channels as $dm) {
|
||
if ($dm['id'] == $active_channel_id) {
|
||
$current_channel_name = $dm['other_user'];
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
$messages = [];
|
||
$current_channel_name = 'Direct Messages';
|
||
}
|
||
$channels = [];
|
||
$members = []; // Members list is different for DMs or hidden
|
||
} else {
|
||
$active_server_id = $_GET['server_id'] ?? ($servers[0]['id'] ?? 1);
|
||
|
||
// Fetch channels
|
||
$stmt = db()->prepare("SELECT * FROM channels WHERE server_id = ? ORDER BY position ASC, id ASC");
|
||
$stmt->execute([$active_server_id]);
|
||
$all_channels = $stmt->fetchAll();
|
||
|
||
require_once 'includes/permissions.php';
|
||
$channels = [];
|
||
foreach($all_channels as $c) {
|
||
if (Permissions::canViewChannel($current_user_id, $c['id'])) {
|
||
$channels[] = $c;
|
||
}
|
||
}
|
||
|
||
$active_channel_id = $_GET['channel_id'] ?? ($channels[0]['id'] ?? 0);
|
||
|
||
// Fetch active channel details for theme
|
||
$active_channel = null;
|
||
foreach($channels as $c) {
|
||
if($c['id'] == $active_channel_id) {
|
||
$active_channel = $c;
|
||
break;
|
||
}
|
||
}
|
||
|
||
$is_owner = false;
|
||
$can_manage_channels = false;
|
||
$can_manage_server = false;
|
||
$active_server = null;
|
||
foreach($servers as $s) {
|
||
if($s['id'] == $active_server_id) {
|
||
$active_server = $s;
|
||
$is_owner = ($s['owner_id'] == $current_user_id);
|
||
$can_manage_channels = Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_CHANNELS) || $is_owner;
|
||
$can_manage_server = Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_SERVER) || Permissions::hasPermission($current_user_id, $active_server_id, Permissions::ADMINISTRATOR) || $is_owner;
|
||
break;
|
||
}
|
||
}
|
||
|
||
$channel_theme = $active_server['theme_color'] ?? null;
|
||
|
||
$channel_type = $active_channel['type'] ?? 'chat';
|
||
$active_thread_id = $_GET['thread_id'] ?? null;
|
||
$active_thread = null;
|
||
|
||
if ($active_thread_id) {
|
||
$stmt = db()->prepare("SELECT t.*, u.username FROM forum_threads t JOIN users u ON t.user_id = u.id WHERE t.id = ?");
|
||
$stmt->execute([$active_thread_id]);
|
||
$active_thread = $stmt->fetch();
|
||
|
||
if ($active_thread) {
|
||
$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,
|
||
(SELECT r.icon_url 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_icon
|
||
FROM messages m
|
||
JOIN users u ON m.user_id = u.id
|
||
WHERE m.thread_id = ?
|
||
ORDER BY m.created_at ASC
|
||
");
|
||
$stmt->execute([$active_server_id, $active_server_id, $active_thread_id]);
|
||
$messages = $stmt->fetchAll();
|
||
}
|
||
}
|
||
|
||
if (!$active_thread && $channel_type === 'rules') {
|
||
$stmt = db()->prepare("SELECT * FROM channel_rules WHERE channel_id = ? ORDER BY position ASC");
|
||
$stmt->execute([$active_channel_id]);
|
||
$rules = $stmt->fetchAll();
|
||
} elseif ($channel_type === 'forum') {
|
||
$filter_status = $_GET['status'] ?? 'all';
|
||
$status_where = "";
|
||
if ($filter_status === 'resolved') {
|
||
$status_where = " AND t.solution_message_id IS NOT NULL";
|
||
} elseif ($filter_status === 'unresolved') {
|
||
$status_where = " AND t.solution_message_id IS NULL";
|
||
}
|
||
|
||
$stmt = db()->prepare("
|
||
SELECT t.*, u.username, u.avatar_url,
|
||
(SELECT COUNT(*) FROM messages m WHERE m.thread_id = t.id) as message_count,
|
||
(SELECT MAX(created_at) FROM messages m WHERE m.thread_id = t.id) as last_activity,
|
||
(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,
|
||
(SELECT r.icon_url 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_icon,
|
||
(SELECT GROUP_CONCAT(CONCAT(ft.name, ':', ft.color) SEPARATOR '|') FROM thread_tags tt JOIN forum_tags ft ON tt.tag_id = ft.id WHERE tt.thread_id = t.id) as tags
|
||
FROM forum_threads t
|
||
JOIN users u ON t.user_id = u.id
|
||
WHERE t.channel_id = ? " . $status_where . "
|
||
ORDER BY last_activity DESC, t.created_at DESC
|
||
");
|
||
$stmt->execute([$active_server_id, $active_server_id, $active_channel_id]);
|
||
$threads = $stmt->fetchAll();
|
||
} else {
|
||
// Fetch messages
|
||
$display_limit = !empty($active_channel['message_limit']) ? (int)$active_channel['message_limit'] : 50;
|
||
$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,
|
||
(SELECT r.icon_url 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_icon
|
||
FROM messages m
|
||
JOIN users u ON m.user_id = u.id
|
||
WHERE m.channel_id = ?
|
||
ORDER BY m.created_at ASC
|
||
LIMIT " . $display_limit . "
|
||
");
|
||
$stmt->execute([$active_server_id, $active_server_id, $active_channel_id]);
|
||
$messages = $stmt->fetchAll();
|
||
}
|
||
|
||
$current_channel_name = 'general';
|
||
foreach($channels as $c) if($c['id'] == $active_channel_id) $current_channel_name = $c['name'];
|
||
|
||
// Fetch members
|
||
$stmt = db()->prepare("
|
||
SELECT u.id, u.username, u.avatar_url, u.status,
|
||
(SELECT GROUP_CONCAT(r.id) FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ?) as role_ids,
|
||
(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,
|
||
(SELECT r.icon_url 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_icon
|
||
FROM users u
|
||
JOIN server_members sm ON u.id = sm.user_id
|
||
WHERE sm.server_id = ?
|
||
");
|
||
$stmt->execute([$active_server_id, $active_server_id, $active_server_id, $active_server_id]);
|
||
$all_server_members = $stmt->fetchAll();
|
||
|
||
$members = [];
|
||
foreach($all_server_members as $m) {
|
||
if (Permissions::canViewChannel($m['id'], $active_channel_id)) {
|
||
$members[] = $m;
|
||
}
|
||
}
|
||
|
||
// Fetch all server roles
|
||
$stmt = db()->prepare("SELECT * FROM roles WHERE server_id = ? ORDER BY position DESC");
|
||
$stmt->execute([$active_server_id]);
|
||
$server_roles = $stmt->fetchAll();
|
||
}
|
||
|
||
// SEO & Env tags
|
||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Discord-like messaging app built with PHP';
|
||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>#<?php echo htmlspecialchars($current_channel_name); ?> | <?php echo htmlspecialchars($projectDescription); ?></title>
|
||
|
||
<meta name="description" content="<?php echo htmlspecialchars($projectDescription); ?>">
|
||
<meta property="og:description" content="<?php echo htmlspecialchars($projectDescription); ?>">
|
||
<meta property="twitter:description" content="<?php echo htmlspecialchars($projectDescription); ?>">
|
||
<?php if ($projectImageUrl): ?>
|
||
<meta property="og:image" content="<?php echo htmlspecialchars($projectImageUrl); ?>">
|
||
<meta property="twitter:image" content="<?php echo htmlspecialchars($projectImageUrl); ?>">
|
||
<?php endif; ?>
|
||
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/css/discord.css?v=<?php echo time(); ?>">
|
||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
||
<script>
|
||
window.currentUserId = <?php echo $current_user_id; ?>;
|
||
window.activeServerId = "<?php echo $active_server_id; ?>";
|
||
window.currentUsername = "<?php echo addslashes($user['username']); ?>";
|
||
window.isServerOwner = <?php echo ($is_owner ?? false) ? 'true' : 'false'; ?>;
|
||
window.canManageServer = <?php echo ($can_manage_server ?? false) ? 'true' : 'false'; ?>;
|
||
window.canManageChannels = <?php echo ($can_manage_channels ?? false) ? 'true' : 'false'; ?>;
|
||
window.activeChannelId = <?php echo $active_channel_id; ?>;
|
||
window.currentChannelName = "<?php echo addslashes($current_channel_name); ?>";
|
||
window.isDndMode = <?php echo ($user['dnd_mode'] ?? 0) ? 'true' : 'false'; ?>;
|
||
window.soundNotifications = <?php echo ($user['sound_notifications'] ?? 0) ? 'true' : 'false'; ?>;
|
||
</script>
|
||
<style>
|
||
:root {
|
||
<?php if ($channel_theme): ?>
|
||
--blurple: <?php echo $channel_theme; ?>;
|
||
<?php endif; ?>
|
||
}
|
||
<?php if ($channel_theme): ?>
|
||
.mention {
|
||
background-color: <?php echo $channel_theme; ?>4D; /* 30% opacity */
|
||
}
|
||
.mention:hover {
|
||
background-color: <?php echo $channel_theme; ?>;
|
||
}
|
||
<?php endif; ?>
|
||
.role-emoji-item:hover {
|
||
background-color: rgba(255,255,255,0.1);
|
||
transform: scale(1.2);
|
||
transition: transform 0.1s;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body data-theme="<?php echo htmlspecialchars($user['theme'] ?? 'dark'); ?>">
|
||
|
||
<div class="discord-app">
|
||
<!-- Servers Sidebar -->
|
||
<div class="servers-sidebar">
|
||
<a href="index.php?server_id=dms" class="server-icon <?php echo $active_server_id == 'dms' ? 'active' : ''; ?>" title="Direct Messages">
|
||
<svg width="28" height="20" viewBox="0 0 28 20" fill="currentColor"><path d="M23.0212 1.67671C21.3107 0.883335 19.4805 0.314845 17.5566 0C17.3137 0.434033 17.0423 1.00252 16.8488 1.46581C14.8193 1.16016 12.8016 1.16016 10.8011 1.46581C10.6076 1.00252 10.3255 0.434033 10.0827 0C8.14811 0.314845 6.31792 0.883335 4.60741 1.67671C1.14775 6.84711 0.210418 11.8962 0.67293 16.8681C2.9723 18.5677 5.19143 19.5997 7.37191 20C7.91578 19.2558 8.3897 18.4616 8.79155 17.6166C7.99616 17.3148 7.2343 16.9416 6.51603 16.505C6.70881 16.3639 6.89745 16.2125 7.07923 16.052C11.4116 18.0494 16.1264 18.0494 20.4137 16.052C20.597 16.2125 20.7856 16.3639 20.9784 16.505C20.2586 16.9416 19.4967 17.3148 18.7013 17.6166C19.1031 18.4616 19.577 19.2558 20.1209 20C22.3014 19.5997 24.5205 18.5677 26.8199 16.8681C27.3693 11.127 25.9189 6.13063 23.0212 1.67671ZM9.51636 13.6749C8.21405 13.6749 7.14188 12.4839 7.14188 11.026C7.14188 9.56816 8.19284 8.3771 9.51636 8.3771C10.8399 8.3771 11.912 9.56816 11.8908 11.026C11.8908 12.4839 10.8399 13.6749 9.51636 13.6749ZM18.0051 13.6749C16.7028 13.6749 15.6306 12.4839 15.6306 11.026C15.6306 9.56816 16.6815 8.3771 18.0051 8.3771C19.3286 8.3771 20.4008 9.56816 20.3796 11.026C20.3796 12.4839 19.3286 13.6749 18.0051 13.6749Z"/></svg>
|
||
</a>
|
||
<hr style="width: 32px; border-color: #35363c; margin: 4px 0;">
|
||
<?php foreach($servers as $s): ?>
|
||
<a href="?server_id=<?php echo $s['id']; ?>"
|
||
class="server-icon <?php echo $s['id'] == $active_server_id ? 'active' : ''; ?>"
|
||
title="<?php echo htmlspecialchars($s['name']); ?>"
|
||
style="<?php
|
||
if (!empty($s['icon_url'])) {
|
||
echo "background-image: url('{$s['icon_url']}'); background-size: cover;";
|
||
} else if (!empty($s['theme_color'])) {
|
||
echo "background-color: {$s['theme_color']};";
|
||
}
|
||
?>">
|
||
<?php echo empty($s['icon_url']) ? mb_substr($s['name'], 0, 1) : ''; ?>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
<a href="#" class="server-icon add-btn" title="Add a Server" data-bs-toggle="modal" data-bs-target="#addServerModal">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Channels Sidebar -->
|
||
<div class="channels-sidebar">
|
||
<div class="channels-header">
|
||
<?php
|
||
if ($is_dm_view) {
|
||
echo "Direct Messages";
|
||
} else {
|
||
$active_server_name = 'Server';
|
||
foreach($servers as $s) {
|
||
if($s['id'] == $active_server_id) {
|
||
$active_server_name = $s['name'];
|
||
break;
|
||
}
|
||
}
|
||
echo htmlspecialchars($active_server_name);
|
||
?>
|
||
<div class="ms-auto d-flex align-items-center">
|
||
<?php if ($can_manage_channels): ?>
|
||
<span class="add-channel-btn me-2" style="cursor: pointer; opacity: 0.7;" data-bs-toggle="modal" data-bs-target="#addChannelModal" data-type="chat" data-category-id="" title="Create Channel">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||
</span>
|
||
<?php endif; ?>
|
||
<?php if ($is_owner || $can_manage_server): ?>
|
||
<span style="cursor: pointer; opacity: 0.7;" data-bs-toggle="modal" data-bs-target="#serverSettingsModal" title="Server Settings">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg>
|
||
</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
?>
|
||
</div>
|
||
<div class="channels-list" id="sidebar-channels-list">
|
||
<?php if ($is_dm_view): ?>
|
||
<?php foreach($dm_channels as $dm): ?>
|
||
<a href="?server_id=dms&channel_id=<?php echo $dm['id']; ?>"
|
||
class="dm-user-item <?php echo $dm['id'] == $active_channel_id ? 'active' : ''; ?>">
|
||
<div class="message-avatar" style="width: 32px; height: 32px; <?php echo $dm['avatar_url'] ? "background-image: url('{$dm['avatar_url']}');" : ""; ?>">
|
||
<div class="dm-status-indicator dm-status-<?php echo $dm['status']; ?>" style="position: absolute; bottom: 0; right: 0;"></div>
|
||
</div>
|
||
<span><?php echo htmlspecialchars($dm['other_user']); ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
<?php else: ?>
|
||
<?php
|
||
// Helper to render a channel item
|
||
function renderChannelItem($c, $active_channel_id, $active_server_id, $can_manage_channels) {
|
||
if ($c['type'] === 'separator') {
|
||
?>
|
||
<div class="channel-item-container separator-item d-flex align-items-center justify-content-between px-2 py-1" data-id="<?php echo $c['id']; ?>" data-type="separator" style="min-height: 24px;">
|
||
<div class="flex-grow-1" style="height: 1px; background: rgba(255,255,255,0.1); margin: 10px 0;"></div>
|
||
<?php if ($can_manage_channels): ?>
|
||
<span class="channel-settings-btn ms-2" style="cursor: pointer; color: var(--text-muted); opacity: 0; transition: opacity 0.2s;"
|
||
data-bs-toggle="modal" data-bs-target="#editChannelModal"
|
||
data-id="<?php echo $c['id']; ?>"
|
||
data-name="separator"
|
||
data-type="separator"
|
||
data-files="0"
|
||
data-limit="0"
|
||
data-status=""
|
||
data-icon="<?php echo htmlspecialchars($c['icon'] ?? ''); ?>"
|
||
data-rules-role="<?php echo $c['rules_role_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>
|
||
</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
return;
|
||
}
|
||
?>
|
||
<div class="channel-item-container d-flex align-items-center" data-id="<?php echo $c['id']; ?>">
|
||
<a href="?server_id=<?php echo $active_server_id; ?>&channel_id=<?php echo $c['id']; ?>"
|
||
class="channel-item flex-grow-1 <?php echo ($c['id'] == $active_channel_id) ? 'active' : ''; ?> <?php echo ($c['type'] === 'voice') ? 'voice-item' : ''; ?>" <?php echo ($c['type'] === 'voice') ? 'data-channel-id="'.$c['id'].'"' : ''; ?>>
|
||
<span class="d-flex align-items-center">
|
||
<span class="me-1" style="width: 20px; display: inline-block; text-align: center;">
|
||
<?php
|
||
if ($c['type'] === 'announcement') echo '<i class="fa-solid fa-bullhorn"></i>';
|
||
elseif ($c['type'] === 'rules') echo '<i class="fa-solid fa-gavel"></i>';
|
||
elseif ($c['type'] === 'forum') echo '<i class="fa-solid fa-comments"></i>';
|
||
elseif ($c['type'] === 'voice') echo '<i class="fa-solid fa-volume-up"></i>';
|
||
else echo '<i class="fa-solid fa-hashtag"></i>';
|
||
?>
|
||
</span>
|
||
<?php if (!empty($c['icon'])): ?>
|
||
<span class="me-1" style="font-size: 14px;"><?php echo renderRoleIcon($c['icon'], '14px'); ?></span>
|
||
<?php endif; ?>
|
||
<span class="channel-name-text"><?php echo htmlspecialchars($c['name']); ?></span>
|
||
</span>
|
||
<?php if ($c['type'] === 'voice' && !empty($c['status'])): ?>
|
||
<div class="channel-status small text-muted ms-4" style="font-size: 0.75em; margin-top: -2px;"><?php echo htmlspecialchars($c['status']); ?></div>
|
||
<?php endif; ?>
|
||
</a>
|
||
<?php if ($can_manage_channels): ?>
|
||
<span class="channel-settings-btn ms-1" style="cursor: pointer; color: var(--text-muted);"
|
||
data-bs-toggle="modal" data-bs-target="#editChannelModal"
|
||
data-id="<?php echo $c['id']; ?>"
|
||
data-name="<?php echo htmlspecialchars($c['name']); ?>"
|
||
data-type="<?php echo $c['type']; ?>"
|
||
data-files="<?php echo $c['allow_file_sharing']; ?>"
|
||
data-limit="<?php echo $c['message_limit']; ?>"
|
||
data-status="<?php echo htmlspecialchars($c['status'] ?? ''); ?>"
|
||
data-icon="<?php echo htmlspecialchars($c['icon'] ?? ''); ?>"
|
||
data-rules-role="<?php echo $c['rules_role_id'] ?? ''; ?>"
|
||
data-category="<?php echo $c['category_id'] ?? ''; ?>">
|
||
<svg width="14" height="14" 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>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
$category_ids = array_column(array_filter($channels, function($c) { return $c['type'] === 'category'; }), 'id');
|
||
|
||
foreach($channels as $item) {
|
||
// 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)) {
|
||
continue;
|
||
}
|
||
|
||
if ($item['type'] === 'category') {
|
||
// Render category and its children
|
||
?>
|
||
<div class="category-wrapper" data-id="<?php echo $item['id']; ?>">
|
||
<div class="channel-category d-flex align-items-center mt-3" data-id="<?php echo $item['id']; ?>">
|
||
<?php if (!empty($item['icon'])): ?>
|
||
<span class="me-1" style="font-size: 14px;"><?php echo renderRoleIcon($item['icon'], '14px'); ?></span>
|
||
<?php endif; ?>
|
||
<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>
|
||
<?php if ($can_manage_channels): ?>
|
||
<span class="channel-settings-btn ms-1" style="cursor: pointer; color: var(--text-muted);"
|
||
data-bs-toggle="modal" data-bs-target="#editChannelModal"
|
||
data-id="<?php echo $item['id']; ?>"
|
||
data-name="<?php echo htmlspecialchars($item['name']); ?>"
|
||
data-type="category"
|
||
data-files="0"
|
||
data-limit="0"
|
||
data-status=""
|
||
data-icon="<?php echo htmlspecialchars($item['icon'] ?? ''); ?>"
|
||
data-rules-role="<?php echo $item['rules_role_id'] ?? ''; ?>"
|
||
data-category=""
|
||
data-theme="">
|
||
<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>
|
||
<?php
|
||
} else {
|
||
// Render top level channel
|
||
renderChannelItem($item, $active_channel_id, $active_server_id, $can_manage_channels);
|
||
}
|
||
}
|
||
?>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="user-panel">
|
||
<div class="user-info" data-bs-toggle="modal" data-bs-target="#userSettingsModal">
|
||
<div class="message-avatar" style="width: 32px; height: 32px; <?php echo $user['avatar_url'] ? "background-image: url('{$user['avatar_url']}');" : ""; ?>"></div>
|
||
<div style="flex: 1; min-width: 0;">
|
||
<div style="font-weight: bold; font-size: 0.85em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||
<?php echo htmlspecialchars($user['username']); ?>
|
||
</div>
|
||
<div style="color: var(--text-muted); font-size: 0.75em;">#<?php echo str_pad($user['id'], 4, '0', STR_PAD_LEFT); ?></div>
|
||
</div>
|
||
</div>
|
||
<div class="user-actions">
|
||
<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-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 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>
|
||
<div style="padding: 10px; font-size: 10px; color: #4e5058; border-top: 1px solid #1e1f22;">
|
||
PHP <?php echo PHP_VERSION; ?> | <?php echo date('H:i'); ?>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Chat Area -->
|
||
<div class="chat-container">
|
||
<div class="chat-header">
|
||
<span style="color: var(--text-muted); margin-right: 8px; width: auto; display: inline-block; text-align: center;">
|
||
<?php
|
||
if ($is_dm_view) {
|
||
echo '@';
|
||
} else {
|
||
if ($active_channel['type'] === 'announcement') echo '<i class="fa-solid fa-bullhorn"></i>';
|
||
elseif ($active_channel['type'] === 'rules') echo '<i class="fa-solid fa-gavel"></i>';
|
||
elseif ($active_channel['type'] === 'forum') echo '<i class="fa-solid fa-comments"></i>';
|
||
elseif ($active_channel['type'] === 'voice') echo '<i class="fa-solid fa-volume-up"></i>';
|
||
else echo '<i class="fa-solid fa-hashtag"></i>';
|
||
|
||
if (!empty($active_channel['icon'])) {
|
||
echo ' <span class="ms-1">' . renderRoleIcon($active_channel['icon'], '16px') . '</span>';
|
||
}
|
||
}
|
||
?>
|
||
</span>
|
||
<span class="flex-grow-1"><?php echo htmlspecialchars($current_channel_name); ?></span>
|
||
|
||
<div class="d-flex align-items-center">
|
||
<button id="toggle-members-btn" class="btn btn-link text-muted p-1 me-2" title="Toggle Members List">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
||
</button>
|
||
<button id="pinned-messages-btn" class="btn btn-link text-muted p-1 me-2" title="Pinned Messages">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
|
||
</button>
|
||
<div class="search-container">
|
||
<div class="input-group input-group-sm">
|
||
<select id="search-type" class="form-select bg-dark text-muted border-0" style="width: auto; max-width: 80px; font-size: 0.7em;">
|
||
<option value="messages">Chat</option>
|
||
<option value="users">Users</option>
|
||
</select>
|
||
<input type="text" id="global-search" class="search-input" placeholder="Search..." autocomplete="off">
|
||
</div>
|
||
<div id="search-results" class="search-results-dropdown"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="messages-list" id="messages-list">
|
||
<?php if($active_thread): ?>
|
||
<div class="thread-view-container p-4">
|
||
<a href="?server_id=<?php echo $active_server_id; ?>&channel_id=<?php echo $active_channel_id; ?><?php echo isset($_GET['status']) ? '&status='.htmlspecialchars($_GET['status']) : ''; ?>" class="btn btn-sm btn-outline-secondary mb-3">← Back to Forum</a>
|
||
<h3>Discussion: <?php echo htmlspecialchars($active_thread['title']); ?></h3>
|
||
<?php
|
||
// Fetch tags for this thread
|
||
$stmt_t = db()->prepare("SELECT ft.* FROM forum_tags ft JOIN thread_tags tt ON ft.id = tt.tag_id WHERE tt.thread_id = ?");
|
||
$stmt_t->execute([$active_thread['id']]);
|
||
$thread_tags = $stmt_t->fetchAll();
|
||
if ($thread_tags):
|
||
foreach ($thread_tags as $t):
|
||
?>
|
||
<span class="badge rounded-pill me-1" style="background-color: <?php echo htmlspecialchars($t['color']); ?>;"><?php echo htmlspecialchars($t['name']); ?></span>
|
||
<?php endforeach; endif; ?>
|
||
<div class="messages-list-inner mt-4">
|
||
<?php foreach($messages as $m):
|
||
$mention_pattern = '/@' . preg_quote($user['username'], '/') . '\b/';
|
||
$is_mentioned = preg_match($mention_pattern, $m['content']);
|
||
$is_solution = ($active_thread['solution_message_id'] == $m['id']);
|
||
?>
|
||
<!-- Message rendering code (reused) -->
|
||
<div class="message-item <?php echo $is_mentioned ? 'mentioned' : ''; ?> <?php echo $is_solution ? 'is-solution' : ''; ?>" data-id="<?php echo $m['id']; ?>">
|
||
<div class="message-avatar" style="<?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>"></div>
|
||
<div class="message-content">
|
||
<div class="message-author" style="<?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
||
<?php echo htmlspecialchars($m['username']); ?>
|
||
<?php echo renderRoleIcon($m['role_icon'], '12px'); ?>
|
||
<span class="message-time"><?php echo date('H:i', strtotime($m['created_at'])); ?></span>
|
||
<?php if ($is_solution): ?>
|
||
<span class="badge bg-success ms-2">SOLUTION</span>
|
||
<?php endif; ?>
|
||
<div class="message-actions-menu">
|
||
<?php if (($active_thread['user_id'] == $current_user_id || $can_manage_server) && $m['user_id'] != $active_thread['user_id']): ?>
|
||
<span class="action-btn mark-solution <?php echo $is_solution ? 'active' : ''; ?>" title="<?php echo $is_solution ? 'Unmark as Solution' : 'Mark as Solution'; ?>" data-thread-id="<?php echo $active_thread['id']; ?>" data-message-id="<?php echo $m['id']; ?>">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||
</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<div class="message-text">
|
||
<?php echo nl2br(htmlspecialchars($m['content'])); ?>
|
||
</div>
|
||
<div class="message-reactions mt-1" data-message-id="<?php echo $m['id']; ?>">
|
||
<?php
|
||
// Fetch reactions for this message
|
||
$stmt_react = db()->prepare("SELECT emoji, COUNT(*) as count, GROUP_CONCAT(user_id) as users FROM message_reactions WHERE message_id = ? GROUP BY emoji");
|
||
$stmt_react->execute([$m['id']]);
|
||
$reactions = $stmt_react->fetchAll();
|
||
foreach ($reactions as $r):
|
||
$reacted = in_array($current_user_id, explode(',', $r['users']));
|
||
?>
|
||
<span class="reaction-badge <?php echo $reacted ? 'active' : ''; ?>" data-emoji="<?php echo htmlspecialchars($r['emoji']); ?>">
|
||
<?php echo htmlspecialchars($r['emoji']); ?> <span class="count"><?php echo $r['count']; ?></span>
|
||
</span>
|
||
<?php endforeach; ?>
|
||
<span class="add-reaction-btn" title="Add Reaction">+</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
<?php elseif($channel_type === 'rules'): ?>
|
||
<div class="rules-container p-4">
|
||
<h2 class="mb-4">📜 <?php echo htmlspecialchars($current_channel_name); ?></h2>
|
||
<div id="rules-list-sortable">
|
||
<?php $i = 1; foreach($rules as $rule): ?>
|
||
<div class="rule-item mb-3 p-3 rounded bg-dark border-start border-4 border-primary d-flex justify-content-between align-items-center" data-id="<?php echo $rule['id']; ?>">
|
||
<div class="rule-content flex-grow-1">
|
||
<span class="rule-number fw-bold me-2"><?php echo $i++; ?>.</span>
|
||
<?php echo nl2br(htmlspecialchars($rule['content'])); ?>
|
||
</div>
|
||
<?php if($can_manage_channels): ?>
|
||
<div class="rule-actions ms-3 d-flex gap-2">
|
||
<button class="btn btn-sm btn-outline-secondary move-rule-btn" data-id="<?php echo $rule['id']; ?>" data-dir="up">↑</button>
|
||
<button class="btn btn-sm btn-outline-secondary move-rule-btn" data-id="<?php echo $rule['id']; ?>" data-dir="down">↓</button>
|
||
<button class="btn btn-sm btn-outline-light edit-rule-btn" data-id="<?php echo $rule['id']; ?>">Edit</button>
|
||
<button class="btn btn-sm btn-outline-danger delete-rule-btn" data-id="<?php echo $rule['id']; ?>">×</button>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php if($can_manage_channels): ?>
|
||
<div id="add-rule-form" style="display: none;" class="mt-3 p-3 rounded bg-dark border border-secondary">
|
||
<textarea id="new-rule-content" class="form-control bg-dark text-white mb-2" placeholder="Saisissez la règle ici..." rows="3"></textarea>
|
||
<div class="d-flex gap-2">
|
||
<button class="btn btn-success btn-sm" id="save-new-rule-btn">Enregistrer</button>
|
||
<button class="btn btn-secondary btn-sm" id="cancel-new-rule-btn">Annuler</button>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-primary mt-3" id="add-rule-btn">+ Ajouter une règle</button>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($active_channel['rules_role_id'])): ?>
|
||
<?php
|
||
$stmtAcc = db()->prepare("SELECT 1 FROM rule_acceptances WHERE user_id = ? AND channel_id = ?");
|
||
$stmtAcc->execute([$current_user_id, $active_channel_id]);
|
||
$has_accepted = $stmtAcc->fetch();
|
||
?>
|
||
<div class="mt-5 pt-4 border-top border-secondary text-center" id="rules-acceptance-container">
|
||
<?php if ($has_accepted): ?>
|
||
<div class="alert alert-success d-inline-block">
|
||
<i class="fa-solid fa-check-circle me-2"></i> Vous avez accepté les règles.
|
||
</div>
|
||
<div class="mt-2">
|
||
<button class="btn btn-sm btn-outline-danger" id="withdraw-rules-btn">
|
||
<i class="fa-solid fa-undo me-1"></i> Retirer mon acceptation
|
||
</button>
|
||
</div>
|
||
<?php else: ?>
|
||
<p class="text-muted mb-3">Veuillez accepter les règles pour obtenir l'accès complet.</p>
|
||
<button class="btn btn-lg btn-success px-5" id="accept-rules-btn">
|
||
<i class="fa-solid fa-check me-2"></i> J'accepte les règles
|
||
</button>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php elseif($channel_type === 'forum'): ?>
|
||
<div class="forum-container p-4">
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<div>
|
||
<h2 class="mb-2">🏛️ <?php echo htmlspecialchars($current_channel_name); ?></h2>
|
||
<div class="btn-group btn-group-sm forum-filters">
|
||
<?php
|
||
$s_id = $active_server_id;
|
||
$c_id = $active_channel_id;
|
||
$curr_status = $_GET['status'] ?? 'all';
|
||
?>
|
||
<a href="?server_id=<?php echo $s_id; ?>&channel_id=<?php echo $c_id; ?>&status=all" class="btn btn-outline-secondary <?php echo $curr_status === 'all' ? 'active' : ''; ?>">All</a>
|
||
<a href="?server_id=<?php echo $s_id; ?>&channel_id=<?php echo $c_id; ?>&status=unresolved" class="btn btn-outline-secondary <?php echo $curr_status === 'unresolved' ? 'active' : ''; ?>">Unresolved</a>
|
||
<a href="?server_id=<?php echo $s_id; ?>&channel_id=<?php echo $c_id; ?>&status=resolved" class="btn btn-outline-secondary <?php echo $curr_status === 'resolved' ? 'active' : ''; ?>">Resolved</a>
|
||
</div>
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<?php if($can_manage_channels): ?>
|
||
<button class="btn btn-outline-secondary" id="manage-tags-btn">Manage Tags</button>
|
||
<?php endif; ?>
|
||
<button class="btn btn-primary" id="new-thread-btn">New Discussion</button>
|
||
</div>
|
||
</div>
|
||
<div class="thread-list">
|
||
<?php if(empty($threads)): ?>
|
||
<div class="text-center text-muted mt-5">No discussions yet. Start one!</div>
|
||
<?php endif; ?>
|
||
<?php foreach($threads as $thread): ?>
|
||
<a href="?server_id=<?php echo $active_server_id; ?>&channel_id=<?php echo $active_channel_id; ?>&thread_id=<?php echo $thread['id']; ?><?php echo isset($_GET['status']) ? '&status='.htmlspecialchars($_GET['status']) : ''; ?>" class="thread-item d-flex align-items-center p-3 mb-2 rounded bg-dark text-decoration-none text-white border-start border-4 border-secondary">
|
||
<div class="thread-icon me-3">💬</div>
|
||
<div class="thread-info flex-grow-1">
|
||
<div class="thread-title fw-bold">
|
||
<?php if($thread['solution_message_id']): ?>
|
||
<span class="text-success me-1" title="Solved">✔</span>
|
||
<?php endif; ?>
|
||
<?php echo htmlspecialchars($thread['title']); ?>
|
||
<?php if($thread['tags']):
|
||
$tag_list = explode('|', $thread['tags']);
|
||
foreach($tag_list as $tag_data):
|
||
list($t_name, $t_color) = explode(':', $tag_data);
|
||
?>
|
||
<span class="badge rounded-pill ms-1" style="background-color: <?php echo htmlspecialchars($t_color); ?>; font-size: 0.6em;"><?php echo htmlspecialchars($t_name); ?></span>
|
||
<?php endforeach; endif; ?>
|
||
</div>
|
||
<div class="thread-meta small text-muted">
|
||
Started by <span style="<?php echo !empty($thread['role_color']) ? "color: {$thread['role_color']};" : ""; ?>"><?php echo htmlspecialchars($thread['username']); ?></span>
|
||
<?php echo renderRoleIcon($thread['role_icon'], '11px'); ?>
|
||
• <?php echo $thread['message_count']; ?> messages
|
||
</div>
|
||
</div>
|
||
<div class="thread-activity text-end small text-muted">
|
||
<?php if($thread['last_activity']): ?>
|
||
Last active: <?php echo date('H:i', strtotime($thread['last_activity'])); ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
<?php else: ?>
|
||
<?php if(empty($messages)): ?>
|
||
<div style="text-align: center; color: var(--text-muted); margin-top: 40px;">
|
||
<h4>Welcome to #<?php echo htmlspecialchars($current_channel_name); ?>!</h4>
|
||
<p>This is the start of the #<?php echo htmlspecialchars($current_channel_name); ?> channel.</p>
|
||
</div>
|
||
<?php endif; ?>
|
||
<?php foreach($messages as $m):
|
||
$mention_pattern = '/@' . preg_quote($user['username'], '/') . '\b/';
|
||
$is_mentioned = preg_match($mention_pattern, $m['content']);
|
||
?>
|
||
<div class="message-item <?php echo $is_mentioned ? 'mentioned' : ''; ?> <?php echo $m['is_pinned'] ? 'pinned' : ''; ?> <?php echo $channel_type === 'announcement' ? 'announcement-style' : ''; ?>" data-id="<?php echo $m['id']; ?>">
|
||
<div class="message-avatar" style="<?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>"></div>
|
||
<div class="message-content">
|
||
<div class="message-header">
|
||
<span class="message-author" style="<?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
||
<?php echo htmlspecialchars($m['username']); ?>
|
||
<?php echo renderRoleIcon($m['role_icon'], '12px'); ?>
|
||
</span>
|
||
<span class="message-time"><?php echo date('H:i', strtotime($m['created_at'])); ?></span>
|
||
<?php if ($m['is_pinned']): ?>
|
||
<span class="pinned-badge ms-2" title="Pinned Message">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path></svg>
|
||
Pinned
|
||
</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="message-text">
|
||
<?php
|
||
$msg_content = htmlspecialchars($m['content']);
|
||
$msg_content = preg_replace($mention_pattern, '<span class="mention">@' . htmlspecialchars($user['username']) . '</span>', $msg_content);
|
||
echo nl2br($msg_content);
|
||
?>
|
||
<?php if ($m['attachment_url']): ?>
|
||
<div class="message-attachment mt-2">
|
||
<?php
|
||
$ext = strtolower(pathinfo($m['attachment_url'], PATHINFO_EXTENSION));
|
||
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])):
|
||
?>
|
||
<img src="<?php echo htmlspecialchars($m['attachment_url']); ?>" class="img-fluid rounded message-img-preview" alt="Attachment" style="max-height: 300px; cursor: pointer;" onclick="window.open(this.src)">
|
||
<?php else: ?>
|
||
<a href="<?php echo htmlspecialchars($m['attachment_url']); ?>" target="_blank" class="attachment-link d-inline-flex align-items-center p-2 rounded bg-dark text-white text-decoration-none">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
||
<?php echo basename($m['attachment_url']); ?>
|
||
</a>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($m['metadata'])):
|
||
$meta = json_decode($m['metadata'], true);
|
||
if ($meta): ?>
|
||
<div class="rich-embed mt-2 p-3 rounded" style="background: rgba(0,0,0,0.1); border-left: 4px solid var(--blurple); max-width: 520px;">
|
||
<?php if (!empty($meta['site_name'])): ?>
|
||
<div class="embed-site-name mb-1" style="font-size: 0.75em; color: var(--text-muted); text-transform: uppercase; font-weight: bold;"><?php echo htmlspecialchars($meta['site_name']); ?></div>
|
||
<?php endif; ?>
|
||
<?php if (!empty($meta['title'])): ?>
|
||
<a href="<?php echo htmlspecialchars($meta['url']); ?>" target="_blank" class="embed-title d-block mb-1 text-decoration-none" style="font-weight: 600; color: #00a8fc;"><?php echo htmlspecialchars($meta['title']); ?></a>
|
||
<?php endif; ?>
|
||
<?php if (!empty($meta['description'])): ?>
|
||
<div class="embed-description mb-2" style="font-size: 0.9em; color: var(--text-normal);"><?php echo htmlspecialchars($meta['description']); ?></div>
|
||
<?php endif; ?>
|
||
<?php if (!empty($meta['image'])): ?>
|
||
<div class="embed-image">
|
||
<img src="<?php echo htmlspecialchars($meta['image']); ?>" class="rounded" style="max-width: 100%; max-height: 300px; object-fit: contain;">
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="message-reactions mt-1" data-message-id="<?php echo $m['id']; ?>">
|
||
<?php
|
||
// Fetch reactions for this message
|
||
$stmt_react = db()->prepare("SELECT emoji, COUNT(*) as count, GROUP_CONCAT(user_id) as users FROM message_reactions WHERE message_id = ? GROUP BY emoji");
|
||
$stmt_react->execute([$m['id']]);
|
||
$reactions = $stmt_react->fetchAll();
|
||
foreach ($reactions as $r):
|
||
$reacted = in_array($current_user_id, explode(',', $r['users']));
|
||
?>
|
||
<span class="reaction-badge <?php echo $reacted ? 'active' : ''; ?>" data-emoji="<?php echo htmlspecialchars($r['emoji']); ?>">
|
||
<?php echo htmlspecialchars($r['emoji']); ?> <span class="count"><?php echo $r['count']; ?></span>
|
||
</span>
|
||
<?php endforeach; ?>
|
||
<span class="add-reaction-btn" title="Add Reaction">+</span>
|
||
</div>
|
||
</div>
|
||
<?php if ($m['user_id'] == $current_user_id || ($active_server_id != 'dms' && $can_manage_channels)): ?>
|
||
<div class="message-actions-menu">
|
||
<span class="action-btn pin <?php echo $m['is_pinned'] ? 'active' : ''; ?>" title="<?php echo $m['is_pinned'] ? 'Unpin' : 'Pin'; ?>" data-id="<?php echo $m['id']; ?>" data-pinned="<?php echo $m['is_pinned'] ? '1' : '0'; ?>">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
|
||
</span>
|
||
<?php if ($m['user_id'] == $current_user_id): ?>
|
||
<span class="action-btn edit" title="Edit" data-id="<?php echo $m['id']; ?>">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
||
</span>
|
||
<span class="action-btn delete" title="Delete" data-id="<?php echo $m['id']; ?>">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
|
||
</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div id="typing-indicator" class="typing-indicator"></div>
|
||
<div class="chat-input-container">
|
||
<?php
|
||
$show_input = true;
|
||
if ($channel_type === 'rules') $show_input = false;
|
||
if ($channel_type === 'forum' && !$active_thread) $show_input = false;
|
||
if ($channel_type === 'announcement' && !$can_manage_channels) $show_input = false;
|
||
|
||
if ($show_input):
|
||
$allow_files = true;
|
||
foreach($channels as $c) {
|
||
if($c['id'] == $active_channel_id) {
|
||
$allow_files = (bool)$c['allow_file_sharing'];
|
||
break;
|
||
}
|
||
}
|
||
?>
|
||
<div id="upload-progress-container" class="upload-progress-container" style="display: none;">
|
||
<div class="progress" style="height: 4px; background-color: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden;">
|
||
<div id="upload-progress-bar" class="progress-bar" role="progressbar" style="width: 0%; background-color: var(--blurple);"></div>
|
||
</div>
|
||
<div class="d-flex justify-content-between mt-1">
|
||
<span id="upload-filename" class="small text-muted" style="font-size: 0.7em;">Uploading...</span>
|
||
<span id="upload-percentage" class="small text-muted" style="font-size: 0.7em;">0%</span>
|
||
</div>
|
||
</div>
|
||
<form id="chat-form" enctype="multipart/form-data">
|
||
<div class="chat-input-wrapper">
|
||
<?php if ($allow_files): ?>
|
||
<label for="file-upload" class="upload-btn-label" title="Upload File">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
||
</label>
|
||
<input type="file" id="file-upload" style="display: none;">
|
||
<?php else: ?>
|
||
<div class="upload-btn-label disabled" title="File sharing disabled" style="opacity: 0.3; cursor: not-allowed;">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="8" x2="16" y2="16"></line><line x1="16" y1="8" x2="8" y2="16"></line></svg>
|
||
</div>
|
||
<?php endif; ?>
|
||
<input type="text" id="chat-input" class="chat-input" placeholder="Message #<?php echo htmlspecialchars($current_channel_name); ?>" autocomplete="off">
|
||
</div>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Members Sidebar -->
|
||
<div class="members-sidebar">
|
||
<div style="color: var(--text-muted); font-size: 0.75em; text-transform: uppercase; font-weight: bold; margin-bottom: 16px;">
|
||
Members — <?php echo count($members); ?>
|
||
</div>
|
||
<?php foreach($members as $m): ?>
|
||
<div class="channel-item member-item" data-user-id="<?php echo $m['id']; ?>" data-username="<?php echo htmlspecialchars($m['username']); ?>" data-avatar="<?php echo htmlspecialchars($m['avatar_url'] ?? ''); ?>" data-role-ids="<?php echo $m['role_ids'] ?? ''; ?>" style="color: var(--text-primary); margin-bottom: 8px; cursor: pointer;">
|
||
<div class="message-avatar" style="width: 32px; height: 32px; background-color: <?php echo $m['status'] == 'online' ? '#23a559' : '#80848e'; ?>; position: relative; <?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>">
|
||
<?php if($m['status'] == 'online'): ?>
|
||
<div style="position: absolute; bottom: 0; right: 0; width: 10px; height: 10px; background-color: #23a559; border-radius: 50%; border: 2px solid var(--bg-members);"></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; <?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
||
<?php echo htmlspecialchars($m['username']); ?>
|
||
<?php echo renderRoleIcon($m['role_icon'], '12px'); ?>
|
||
</span>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- User Settings Modal -->
|
||
<div class="modal fade" id="userSettingsModal" tabindex="-1">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">User Settings</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="user-settings-form">
|
||
<div class="row">
|
||
<div class="col-md-4 text-center">
|
||
<div class="message-avatar mx-auto mb-3" id="settings-avatar-preview" style="width: 100px; height: 100px; <?php echo $user['avatar_url'] ? "background-image: url('{$user['avatar_url']}');" : ""; ?>"></div>
|
||
<input type="hidden" name="avatar_url" id="settings-avatar-url" value="<?php echo htmlspecialchars($user['avatar_url'] ?? ''); ?>">
|
||
<p class="small text-muted">Pick an avatar from Pexels or search for one.</p>
|
||
</div>
|
||
<div class="col-md-8">
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Username</label>
|
||
<input type="text" name="username" class="form-control" value="<?php echo htmlspecialchars($user['username']); ?>" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Settings</label>
|
||
<div class="form-check form-switch mb-2">
|
||
<input class="form-check-input" type="checkbox" name="dnd_mode" id="dnd-switch" value="1" <?php echo ($user['dnd_mode'] ?? 0) ? 'checked' : ''; ?>>
|
||
<label class="form-check-label text-white" for="dnd-switch">Do Not Disturb</label>
|
||
<div class="form-text text-muted" style="font-size: 0.8em;">Mute all desktop notifications.</div>
|
||
</div>
|
||
<div class="form-check form-switch mb-2">
|
||
<input class="form-check-input" type="checkbox" name="sound_notifications" id="sound-switch" value="1" <?php echo ($user['sound_notifications'] ?? 0) ? 'checked' : ''; ?>>
|
||
<label class="form-check-label text-white" for="sound-switch">Sound Notifications</label>
|
||
<div class="form-text text-muted" style="font-size: 0.8em;">Play a sound when you are mentioned.</div>
|
||
</div>
|
||
<div class="mb-2">
|
||
<label class="form-label text-white d-block" style="font-size: 0.9em;">Appearance</label>
|
||
<div class="btn-group w-100" role="group">
|
||
<input type="radio" class="btn-check" name="theme" id="theme-dark" value="dark" <?php echo ($user['theme'] ?? 'dark') == 'dark' ? 'checked' : ''; ?>>
|
||
<label class="btn btn-outline-secondary btn-sm" for="theme-dark">Dark</label>
|
||
<input type="radio" class="btn-check" name="theme" id="theme-light" value="light" <?php echo ($user['theme'] ?? 'dark') == 'light' ? 'checked' : ''; ?>>
|
||
<label class="btn btn-outline-secondary btn-sm" for="theme-light">Light</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Search Avatars</label>
|
||
<div class="input-group mb-2">
|
||
<input type="text" id="avatar-search-query" class="form-control" placeholder="e.g. cat, abstract, gamer">
|
||
<button class="btn btn-outline-secondary" type="button" id="search-avatar-btn">Search</button>
|
||
</div>
|
||
<div id="avatar-results" class="d-flex flex-wrap gap-2 overflow-auto" style="max-height: 200px;">
|
||
<!-- Pexels results here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-link text-white text-decoration-none" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="button" id="save-settings-btn" class="btn btn-primary" style="background-color: var(--blurple); border: none; padding: 10px 24px;">Save Changes</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Server Settings Modal -->
|
||
<div class="modal fade" id="serverSettingsModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Server Settings</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body p-0">
|
||
<ul class="nav nav-tabs nav-fill" id="serverSettingsTabs" role="tablist">
|
||
<li class="nav-item">
|
||
<button class="nav-link active text-white border-0 bg-transparent" data-bs-toggle="tab" data-bs-target="#settings-general" type="button">General</button>
|
||
</li>
|
||
<li class="nav-item">
|
||
<button class="nav-link text-white border-0 bg-transparent" id="roles-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-roles" type="button">Roles</button>
|
||
</li>
|
||
<li class="nav-item">
|
||
<button class="nav-link text-white border-0 bg-transparent" id="webhooks-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-webhooks" type="button">Webhooks</button>
|
||
</li>
|
||
<li class="nav-item">
|
||
<button class="nav-link text-white border-0 bg-transparent" id="members-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-members" type="button">Members</button>
|
||
</li>
|
||
<li class="nav-item">
|
||
<button class="nav-link text-white border-0 bg-transparent" id="stats-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-stats" type="button">Stats</button>
|
||
</li>
|
||
</ul>
|
||
<div class="tab-content p-3">
|
||
<div class="tab-pane fade show active" id="settings-general">
|
||
<form action="api_v1_servers.php" method="POST" id="server-settings-form">
|
||
<input type="hidden" name="action" value="update">
|
||
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
|
||
|
||
<div class="mb-3 text-center">
|
||
<?php
|
||
$active_icon = '';
|
||
foreach($servers as $s) if($s['id'] == $active_server_id) $active_icon = $s['icon_url'];
|
||
?>
|
||
<div class="message-avatar mx-auto mb-2" id="server-icon-preview" style="width: 80px; height: 80px; <?php echo $active_icon ? "background-image: url('{$active_icon}');" : ""; ?>"></div>
|
||
<input type="hidden" name="icon_url" id="server-icon-url" value="<?php echo htmlspecialchars($active_icon); ?>">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" id="search-server-icon-btn">Change Icon</button>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Server Name</label>
|
||
<input type="text" name="name" class="form-control" value="<?php echo htmlspecialchars($active_server_name ?? ''); ?>" required>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Theme Color</label>
|
||
<input type="color" name="theme_color" class="form-control form-control-color w-100" value="<?php echo $active_server['theme_color'] ?? '#5865f2'; ?>" title="Choose server theme color">
|
||
</div>
|
||
|
||
<div id="server-icon-search-results" class="d-flex flex-wrap gap-2 mb-3 overflow-auto" style="max-height: 150px;"></div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Invite Code</label>
|
||
<?php
|
||
$invite = '';
|
||
foreach($servers as $s) if($s['id'] == $active_server_id) $invite = $s['invite_code'];
|
||
?>
|
||
<div class="input-group">
|
||
<input type="text" class="form-control bg-dark text-white border-0" value="<?php echo $invite; ?>" readonly>
|
||
<button class="btn btn-secondary" type="button" onclick="navigator.clipboard.writeText('<?php echo $invite; ?>')">Copy</button>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="border-secondary">
|
||
|
||
<button type="submit" class="btn btn-primary w-100 mb-2">Save Changes</button>
|
||
</form>
|
||
<form action="api_v1_servers.php" method="POST" onsubmit="return confirm('Are you sure you want to delete this server? This action is irreversible.');">
|
||
<input type="hidden" name="action" value="delete">
|
||
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
|
||
<button type="submit" class="btn btn-danger w-100">Delete Server</button>
|
||
</form>
|
||
</div>
|
||
<div class="tab-pane fade" id="settings-roles">
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<h6 class="mb-0">Server Roles</h6>
|
||
<button class="btn btn-sm btn-primary" id="add-role-btn">+ Add Role</button>
|
||
</div>
|
||
<div id="roles-list" class="list-group list-group-flush bg-transparent">
|
||
<!-- Roles will be loaded here -->
|
||
</div>
|
||
</div>
|
||
<div class="tab-pane fade" id="settings-webhooks">
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<h6 class="mb-0">Webhooks</h6>
|
||
<button class="btn btn-sm btn-primary" id="add-webhook-btn">+ Create Webhook</button>
|
||
</div>
|
||
<div id="webhooks-list" class="list-group list-group-flush bg-transparent">
|
||
<!-- Webhooks will be loaded here -->
|
||
</div>
|
||
</div>
|
||
<div class="tab-pane fade" id="settings-members">
|
||
<h6 class="mb-3">Server Members</h6>
|
||
<div id="server-members-list" class="list-group list-group-flush bg-transparent">
|
||
<!-- Members will be loaded here -->
|
||
</div>
|
||
</div>
|
||
<div class="tab-pane fade" id="settings-stats">
|
||
<div id="stats-content">
|
||
<div class="row text-center mb-4">
|
||
<div class="col-6">
|
||
<div class="p-3 rounded bg-dark">
|
||
<div class="small text-muted text-uppercase">Members</div>
|
||
<div class="h4 mb-0" id="stat-members">-</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6">
|
||
<div class="p-3 rounded bg-dark">
|
||
<div class="small text-muted text-uppercase">Messages</div>
|
||
<div class="h4 mb-0" id="stat-messages">-</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h6 class="text-uppercase small text-muted mb-2">Top Active Users</h6>
|
||
<div id="top-users-list" class="mb-4">
|
||
<!-- Top users here -->
|
||
</div>
|
||
<h6 class="text-uppercase small text-muted mb-2">Activity (Last 7 Days)</h6>
|
||
<div id="activity-chart-placeholder" class="small text-muted text-center p-4 border border-secondary rounded">
|
||
<!-- Simplistic chart or list -->
|
||
Loading activity...
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Server Modal -->
|
||
<div class="modal fade" id="addServerModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Create or Join a server</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body p-0">
|
||
<ul class="nav nav-tabs nav-fill" id="serverTabs" role="tablist">
|
||
<li class="nav-item">
|
||
<button class="nav-link active text-white border-0 bg-transparent" id="create-tab" data-bs-toggle="tab" data-bs-target="#create-pane" type="button">Create</button>
|
||
</li>
|
||
<li class="nav-item">
|
||
<button class="nav-link text-white border-0 bg-transparent" id="join-tab" data-bs-toggle="tab" data-bs-target="#join-pane" type="button">Join</button>
|
||
</li>
|
||
</ul>
|
||
<div class="tab-content p-3">
|
||
<div class="tab-pane fade show active" id="create-pane">
|
||
<form action="api_v1_servers.php" method="POST">
|
||
<p style="color: var(--text-muted); font-size: 0.9em;">Give your new server a personality with a name.</p>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Server Name</label>
|
||
<input type="text" name="name" class="form-control" placeholder="My Awesome Server" required>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary w-100" style="background-color: var(--blurple); border: none;">Create Server</button>
|
||
</form>
|
||
</div>
|
||
<div class="tab-pane fade" id="join-pane">
|
||
<form action="api_v1_servers.php" method="POST">
|
||
<input type="hidden" name="action" value="join">
|
||
<p style="color: var(--text-muted); font-size: 0.9em;">Enter an invite code to join an existing server.</p>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Invite Code</label>
|
||
<input type="text" name="invite_code" class="form-control" placeholder="GEN-123" required>
|
||
</div>
|
||
<button type="submit" class="btn btn-success w-100" style="background-color: #23a559; border: none;">Join Server</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Channel Modal -->
|
||
<div class="modal fade" id="addChannelModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Create Channel</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<form action="api_v1_channels.php" method="POST">
|
||
<input type="hidden" name="action" value="create">
|
||
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Channel Type</label>
|
||
<select name="type" class="form-select bg-dark text-white border-secondary mb-3" id="add-channel-type">
|
||
<option value="chat">Traditional Chat</option>
|
||
<option value="announcement">Announcements</option>
|
||
<option value="rules">Rules</option>
|
||
<option value="forum">Forum</option>
|
||
<option value="voice">Voice Channel</option>
|
||
<option value="separator">Separator</option>
|
||
<option value="category">Category</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Channel Name</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-dark border-0 text-muted" id="add-channel-prefix">#</span>
|
||
<input type="text" name="name" class="form-control" placeholder="new-channel" required>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Channel Icon</label>
|
||
<select name="icon" class="form-select bg-dark text-white border-secondary mb-3">
|
||
<option value="">Aucune icône personnalisée</option>
|
||
<option value="fa-hashtag"># Hashtag</option>
|
||
<option value="fa-volume-up">🔊 Voice</option>
|
||
<option value="fa-bullhorn">📢 Announcements</option>
|
||
<option value="fa-gavel">🔨 Rules</option>
|
||
<option value="fa-comments">💬 Forum</option>
|
||
<option value="fa-lock">🔒 Private</option>
|
||
<option value="fa-star">⭐ Star</option>
|
||
<option value="fa-heart">❤️ Heart</option>
|
||
<option value="fa-gamepad">🎮 Gaming</option>
|
||
<option value="fa-music">🎵 Music</option>
|
||
<option value="fa-video">📹 Video</option>
|
||
<option value="fa-info-circle">ℹ️ Info</option>
|
||
<option value="fa-question-circle">❓ Help</option>
|
||
<option value="fa-book">📖 Library</option>
|
||
<option value="fa-gift">🎁 Giveaways</option>
|
||
<option value="fa-code">💻 Coding</option>
|
||
<option value="fa-terminal">⌨️ Bot</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3" id="add-channel-rules-role-container" style="display: none;">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Role granted upon acceptance</label>
|
||
<select name="rules_role_id" class="form-select bg-dark text-white border-secondary">
|
||
<option value="">No role assigned</option>
|
||
<?php foreach($server_roles as $role): ?>
|
||
<option value="<?php echo $role['id']; ?>"><?php echo htmlspecialchars($role['name']); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="form-check form-switch mb-3">
|
||
<input class="form-check-input" type="checkbox" name="allow_file_sharing" id="add-channel-files" value="1" checked>
|
||
<label class="form-check-label text-white" for="add-channel-files">Allow File Sharing</label>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Message Limit</label>
|
||
<input type="number" name="message_limit" class="form-control" placeholder="e.g. 50 (Leave empty for no limit)">
|
||
<div class="form-text text-muted" style="font-size: 0.8em;">Automatically keeps only the last X messages in this channel.</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-link text-white text-decoration-none" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="submit" class="btn btn-primary" style="background-color: var(--blurple); border: none; padding: 10px 24px;">Create Channel</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Edit Channel Modal -->
|
||
<div class="modal fade" id="editChannelModal" tabindex="-1">
|
||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||
<div class="modal-content border-0 shadow-lg" style="background-color: #313338;">
|
||
<div class="modal-header border-0 pb-0">
|
||
<h5 class="modal-title fw-bold">Channel Settings — #<span id="header-channel-name"></span></h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body p-0">
|
||
<ul class="nav nav-tabs nav-fill px-3 border-bottom border-secondary" id="editChannelTabs" role="tablist" style="background-color: #2b2d31;">
|
||
<li class="nav-item">
|
||
<button class="nav-link active text-white border-0 bg-transparent py-3" data-bs-toggle="tab" data-bs-target="#edit-channel-general" type="button">Overview</button>
|
||
</li>
|
||
<li class="nav-item" id="rss-tab-nav" style="display: none;">
|
||
<button class="nav-link text-white border-0 bg-transparent py-3" id="rss-tab-btn" data-bs-toggle="tab" data-bs-target="#edit-channel-rss" type="button">RSS Feeds</button>
|
||
</li>
|
||
<li class="nav-item">
|
||
<button class="nav-link text-white border-0 bg-transparent py-3" id="channel-permissions-tab-btn" data-bs-toggle="tab" data-bs-target="#edit-channel-permissions" type="button">Permissions</button>
|
||
</li>
|
||
</ul>
|
||
<div class="tab-content p-4" style="min-height: 450px;">
|
||
<div class="tab-pane fade show active" id="edit-channel-general">
|
||
<form action="api_v1_channels.php" method="POST">
|
||
<input type="hidden" name="action" value="update">
|
||
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
|
||
<input type="hidden" name="channel_id" id="edit-channel-id">
|
||
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Channel Name</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-dark border-secondary text-muted">#</span>
|
||
<input type="text" name="name" id="edit-channel-name" class="form-control bg-dark text-white border-secondary" required>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Channel Type</label>
|
||
<select name="type" id="edit-channel-type" class="form-select bg-dark text-white border-secondary">
|
||
<option value="chat">Text Channel</option>
|
||
<option value="announcement">Announcements</option>
|
||
<option value="rules">Rules</option>
|
||
<option value="forum">Forum</option>
|
||
<option value="voice">Voice Channel</option>
|
||
<option value="category">Category</option>
|
||
<option value="separator">Separator</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Category</label>
|
||
<select name="category_id" id="edit-channel-category-id" class="form-select bg-dark text-white border-secondary">
|
||
<option value="">No Category</option>
|
||
<?php
|
||
foreach($all_channels as $cat) {
|
||
if ($cat['type'] === 'category') {
|
||
echo '<option value="'.$cat['id'].'">'.htmlspecialchars($cat['name']).'</option>';
|
||
}
|
||
}
|
||
?>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Icon</label>
|
||
<select name="icon" id="edit-channel-icon" class="form-select bg-dark text-white border-secondary">
|
||
<option value="">Aucune icône personnalisée</option>
|
||
<option value="fa-hashtag"># Hashtag</option>
|
||
<option value="fa-volume-up">🔊 Voice</option>
|
||
<option value="fa-bullhorn">📢 Announcements</option>
|
||
<option value="fa-gavel">🔨 Rules</option>
|
||
<option value="fa-comments">💬 Forum</option>
|
||
<option value="fa-lock">🔒 Private</option>
|
||
<option value="fa-star">⭐ Star</option>
|
||
<option value="fa-heart">❤️ Heart</option>
|
||
<option value="fa-gamepad">🎮 Gaming</option>
|
||
<option value="fa-music">🎵 Music</option>
|
||
<option value="fa-video">📹 Video</option>
|
||
<option value="fa-info-circle">ℹ️ Info</option>
|
||
<option value="fa-question-circle">❓ Help</option>
|
||
<option value="fa-book">📖 Library</option>
|
||
<option value="fa-gift">🎁 Giveaways</option>
|
||
<option value="fa-code">💻 Coding</option>
|
||
<option value="fa-terminal">⌨️ Bot</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3" id="edit-channel-rules-role-container" style="display: none;">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Role granted upon acceptance</label>
|
||
<select name="rules_role_id" id="edit-channel-rules-role" class="form-select bg-dark text-white border-secondary">
|
||
<option value="">No role assigned</option>
|
||
<?php foreach($server_roles as $role): ?>
|
||
<option value="<?php echo $role['id']; ?>"><?php echo htmlspecialchars($role['name']); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row mt-2">
|
||
<div class="col-12">
|
||
<div class="mb-3" id="edit-channel-status-container" style="display: none;">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Voice Status</label>
|
||
<input type="text" name="status" id="edit-channel-status" class="form-control bg-dark text-white border-secondary" placeholder="What's happening?">
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Message Retention</label>
|
||
<input type="number" name="message_limit" id="edit-channel-limit" class="form-control bg-dark text-white border-secondary" placeholder="Keep all messages">
|
||
</div>
|
||
|
||
<div class="form-check form-switch mb-4">
|
||
<input class="form-check-input" type="checkbox" name="allow_file_sharing" id="edit-channel-files" value="1">
|
||
<label class="form-check-label text-white" for="edit-channel-files">Allow users to upload files</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-flex gap-2">
|
||
<button type="submit" class="btn btn-primary flex-grow-1">Save Changes</button>
|
||
<button type="button" id="clear-channel-history-btn" class="btn btn-outline-warning">Clear Chat</button>
|
||
<button type="button" class="btn btn-outline-danger" id="delete-channel-trigger">Delete</button>
|
||
</div>
|
||
</form>
|
||
|
||
<div id="delete-confirm-zone" style="display: none;" class="mt-3 p-3 border border-danger rounded">
|
||
<p class="text-danger small mb-2">Are you sure? This cannot be undone.</p>
|
||
<form action="api_v1_channels.php" method="POST" class="d-flex gap-2">
|
||
<input type="hidden" name="action" value="delete">
|
||
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
|
||
<input type="hidden" name="channel_id" id="delete-channel-id">
|
||
<button type="submit" class="btn btn-danger btn-sm">Yes, Delete Channel</button>
|
||
<button type="button" class="btn btn-secondary btn-sm" onclick="document.getElementById('delete-confirm-zone').style.display='none'">Cancel</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-pane fade" id="edit-channel-rss">
|
||
<div class="mb-4">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Add RSS Feed</label>
|
||
<div class="input-group">
|
||
<input type="url" id="new-rss-url" class="form-control bg-dark text-white border-secondary" placeholder="https://example.com/feed.xml">
|
||
<button class="btn btn-primary" type="button" id="add-rss-btn">Add</button>
|
||
</div>
|
||
</div>
|
||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
<label class="form-label text-uppercase fw-bold mb-0" style="font-size: 0.7em; color: var(--text-muted);">Active Subscriptions</label>
|
||
<button class="btn btn-sm btn-outline-info" id="sync-rss-btn">Sync All</button>
|
||
</div>
|
||
<div id="rss-feeds-list" class="list-group list-group-flush bg-transparent border rounded border-secondary" style="max-height: 250px; overflow-y: auto;">
|
||
<!-- RSS feeds loaded here -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-pane fade h-100" id="edit-channel-permissions">
|
||
<div class="row h-100 g-0 border rounded border-secondary overflow-hidden" style="background-color: #2b2d31;">
|
||
<!-- Sidebar: Roles & Members -->
|
||
<div class="col-4 border-end border-secondary d-flex flex-column" style="background-color: #2b2d31;">
|
||
<div class="p-3 border-bottom border-secondary d-flex justify-content-between align-items-center">
|
||
<span class="small fw-bold text-uppercase" style="font-size: 0.7em; color: #dbdee1;">Roles / Members</span>
|
||
<div class="dropdown">
|
||
<button class="btn btn-sm btn-link text-white p-0" type="button" data-bs-toggle="dropdown" title="Add Role or Member" style="text-decoration: none; opacity: 0.8;">
|
||
<i class="fa-solid fa-plus-circle" style="font-size: 1.1rem;"></i>
|
||
</button>
|
||
<ul class="dropdown-menu dropdown-menu-dark shadow border-secondary" id="add-permission-role-list" style="max-height: 300px; overflow-y: auto; min-width: 200px;">
|
||
<!-- Roles loaded here -->
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div id="channel-permissions-roles-list" class="list-group list-group-flush overflow-auto flex-grow-1" style="max-height: 350px; overflow-x: hidden;">
|
||
<!-- List of roles with overrides -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main: Permission Settings -->
|
||
<div class="col-8 d-flex flex-column" style="background-color: #313338;">
|
||
<div id="channel-permissions-settings" class="h-100 d-flex flex-column d-none">
|
||
<div class="p-3 border-bottom border-secondary d-flex justify-content-between align-items-center">
|
||
<h6 class="mb-0 fw-bold" id="selected-perm-role-name">Role Name</h6>
|
||
<button class="btn btn-sm btn-outline-danger py-0 px-2" id="remove-selected-perm-role" style="font-size: 0.75em;">Clear Overrides</button>
|
||
</div>
|
||
<div class="p-3 overflow-auto flex-grow-1" id="permissions-toggles-container" style="max-height: 350px; overflow-x: hidden;">
|
||
<div class="permission-item mb-3 p-2 rounded" style="background: rgba(255,255,255,0.02);">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div class="pe-3">
|
||
<div class="fw-bold" style="color: #ffffff; font-size: 0.9em;">View Channel</div>
|
||
<div style="font-size: 0.75em; color: #b5bac1;">Allows members to view this channel.</div>
|
||
</div>
|
||
<div class="btn-group btn-group-sm perm-tri-state" data-perm-bit="1">
|
||
<input type="radio" class="btn-check" name="perm_1" id="perm_1_deny" value="deny">
|
||
<label class="btn btn-outline-danger border-0" for="perm_1_deny" title="Deny"><i class="fa-solid fa-xmark"></i></label>
|
||
|
||
<input type="radio" class="btn-check" name="perm_1" id="perm_1_neutral" value="neutral" checked>
|
||
<label class="btn btn-outline-secondary border-0" for="perm_1_neutral" title="Neutral">/</label>
|
||
|
||
<input type="radio" class="btn-check" name="perm_1" id="perm_1_allow" value="allow">
|
||
<label class="btn btn-outline-success border-0" for="perm_1_allow" title="Allow"><i class="fa-solid fa-check"></i></label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- More permissions can be added here -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h-100 d-flex align-items-center justify-content-center text-muted p-4" id="no-role-selected-view">
|
||
<div class="text-center">
|
||
<i class="fa-solid fa-lock mb-3" style="font-size: 2.5rem; opacity: 0.2;"></i>
|
||
<p class="small mb-0">Select a role or member on the left to configure their specific permissions for this channel.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Pinned Messages Modal -->
|
||
<div class="modal fade" id="pinnedMessagesModal" tabindex="-1">
|
||
<div class="modal-dialog modal-md">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Pinned Messages</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body p-0" id="pinned-messages-container" style="max-height: 500px; overflow-y: auto; background-color: var(--bg-chat);">
|
||
<div class="p-3 text-center text-muted">No pinned messages yet.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Role Editor Modal -->
|
||
<div class="modal fade" id="roleEditorModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Edit Role</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="edit-role-id">
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Role Name</label>
|
||
<input type="text" id="edit-role-name" class="form-control">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Role Color</label>
|
||
<input type="color" id="edit-role-color" class="form-control form-control-color w-100">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Role Icon</label>
|
||
<div class="d-flex align-items-center mb-2">
|
||
<div id="selected-role-emoji-preview" class="d-flex align-items-center justify-content-center border rounded" style="width: 40px; height: 40px; font-size: 24px; background: rgba(0,0,0,0.2);"></div>
|
||
<input type="hidden" id="edit-role-icon">
|
||
<button type="button" class="btn btn-sm btn-outline-danger ms-2" onclick="document.getElementById('edit-role-icon').value=''; document.getElementById('selected-role-emoji-preview').textContent='';">Remove Icon</button>
|
||
</div>
|
||
<div id="role-emoji-categories" class="d-flex overflow-auto mb-2 p-1" style="background: rgba(0,0,0,0.1); border-radius: 4px; gap: 5px;">
|
||
<!-- Categories populated by JS -->
|
||
</div>
|
||
<div class="mb-2">
|
||
<input type="text" id="role-emoji-search" class="form-control form-control-sm bg-dark border-secondary text-white" placeholder="Search emoji...">
|
||
</div>
|
||
<div id="role-emoji-grid" class="border rounded p-2" style="max-height: 150px; overflow-y: auto; display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; background: rgba(0,0,0,0.2);">
|
||
<!-- Emojis populated by JS -->
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Permissions</label>
|
||
<div id="role-permissions-checkboxes" class="p-2 bg-dark rounded">
|
||
<!-- Permission checkboxes here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="button" id="save-role-btn" class="btn btn-primary">Save Role</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- New Thread Modal -->
|
||
<div class="modal fade" id="newThreadModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">New Discussion</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Thread Title</label>
|
||
<input type="text" id="new-thread-title" class="form-control" placeholder="What's on your mind?">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Tags</label>
|
||
<div id="new-thread-tags-list" class="d-flex flex-wrap gap-2 p-2 bg-dark rounded">
|
||
<!-- Tags loaded here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="button" id="submit-new-thread-btn" class="btn btn-primary">Create Thread</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Manage Tags Modal -->
|
||
<div class="modal fade" id="manageTagsModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Manage Forum Tags</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="forum-tags-admin-list" class="mb-4">
|
||
<!-- List of tags with delete buttons -->
|
||
</div>
|
||
<hr class="border-secondary">
|
||
<h6>Add New Tag</h6>
|
||
<div class="row g-2">
|
||
<div class="col-7">
|
||
<input type="text" id="new-tag-name" class="form-control form-control-sm" placeholder="Tag Name">
|
||
</div>
|
||
<div class="col-3">
|
||
<input type="color" id="new-tag-color" class="form-control form-control-sm form-control-color w-100" value="#7289da">
|
||
</div>
|
||
<div class="col-2">
|
||
<button class="btn btn-sm btn-primary w-100" id="add-forum-tag-btn">+</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Edit User Roles Modal -->
|
||
<div class="modal fade" id="editUserRolesModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Edit Member Roles</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="d-flex align-items-center mb-4">
|
||
<div id="edit-user-roles-avatar" class="message-avatar me-3" style="width: 48px; height: 48px;"></div>
|
||
<div>
|
||
<h5 id="edit-user-roles-username" class="mb-0">Username</h5>
|
||
<div class="text-muted small">Select roles to assign to this member</div>
|
||
</div>
|
||
</div>
|
||
<input type="hidden" id="edit-user-roles-user-id">
|
||
<div id="user-roles-selection-list" class="list-group list-group-flush bg-dark rounded">
|
||
<!-- Roles checkboxes populated by JS -->
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="button" id="save-user-roles-btn" class="btn btn-primary">Save Changes</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script>
|
||
window.currentUserId = <?php echo $current_user_id; ?>;
|
||
window.currentUsername = "<?php echo addslashes($user['username']); ?>";
|
||
window.currentChannelName = "<?php echo addslashes($current_channel_name); ?>";
|
||
window.isServerOwner = <?php echo ($is_owner ?? false) ? 'true' : 'false'; ?>;
|
||
window.canManageServer = <?php echo ($can_manage_server ?? false) ? 'true' : 'false'; ?>;
|
||
window.isDndMode = <?php echo ($user['dnd_mode'] ?? 0) ? 'true' : 'false'; ?>;
|
||
</script>
|
||
<script src="assets/js/voice.js?v=<?php echo time(); ?>"></script>
|
||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||
<script>
|
||
// Handle channel type and icon in modals
|
||
const addChannelType = document.getElementById('add-channel-type');
|
||
const addChannelPrefix = document.getElementById('add-channel-prefix');
|
||
const addChannelIcon = document.querySelector('#addChannelModal select[name="icon"]');
|
||
|
||
const editChannelType = document.getElementById('edit-channel-type');
|
||
const editChannelPrefix = document.getElementById('edit-channel-prefix');
|
||
const editChannelIcon = document.getElementById('edit-channel-icon');
|
||
|
||
function getPrefixForType(type) {
|
||
if (type === 'voice') return '<i class="fa-solid fa-volume-up"></i>';
|
||
if (type === 'announcement') return '<i class="fa-solid fa-bullhorn"></i>';
|
||
if (type === 'rules') return '<i class="fa-solid fa-gavel"></i>';
|
||
if (type === 'forum') return '<i class="fa-solid fa-comments"></i>';
|
||
if (type === 'separator') return '—';
|
||
return '#';
|
||
}
|
||
|
||
function updatePrefix(typeSelect, iconSelect, prefixSpan) {
|
||
if (!prefixSpan || !typeSelect) return;
|
||
|
||
// 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;
|
||
}
|
||
|
||
let prefix = getPrefixForType(typeSelect.value);
|
||
if (iconSelect && iconSelect.value) {
|
||
prefix += ` <i class="fa-solid ${iconSelect.value}"></i>`;
|
||
}
|
||
prefixSpan.innerHTML = prefix;
|
||
}
|
||
|
||
document.querySelectorAll('.add-channel-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const type = this.getAttribute('data-type');
|
||
const categoryId = this.getAttribute('data-category-id');
|
||
if (addChannelType) {
|
||
addChannelType.value = type;
|
||
updatePrefix(addChannelType, addChannelIcon, addChannelPrefix);
|
||
}
|
||
|
||
// Add category_id hidden field or select it if we add it to addChannelModal
|
||
// Let's add a hidden field to addChannelModal for category_id
|
||
let catInput = document.getElementById('add-channel-category-id');
|
||
if (!catInput) {
|
||
catInput = document.createElement('input');
|
||
catInput.type = 'hidden';
|
||
catInput.name = 'category_id';
|
||
catInput.id = 'add-channel-category-id';
|
||
document.querySelector('#addChannelModal form').appendChild(catInput);
|
||
}
|
||
catInput.value = categoryId || '';
|
||
});
|
||
});
|
||
|
||
if (addChannelType) {
|
||
addChannelType.addEventListener('change', () => updatePrefix(addChannelType, addChannelIcon, addChannelPrefix));
|
||
}
|
||
if (addChannelIcon) {
|
||
addChannelIcon.addEventListener('change', () => updatePrefix(addChannelType, addChannelIcon, addChannelPrefix));
|
||
}
|
||
|
||
if (editChannelType) {
|
||
editChannelType.addEventListener('change', () => updatePrefix(editChannelType, editChannelIcon, editChannelPrefix));
|
||
}
|
||
if (editChannelIcon) {
|
||
editChannelIcon.addEventListener('change', () => updatePrefix(editChannelType, editChannelIcon, editChannelPrefix));
|
||
}
|
||
|
||
// Initial update when opening edit modal
|
||
document.addEventListener('click', function(e) {
|
||
const btn = e.target.closest('.channel-settings-btn');
|
||
if (btn) {
|
||
// Fill basic fields to ensure they are present even if main.js fails
|
||
const modal = document.getElementById('editChannelModal');
|
||
if (modal) {
|
||
const idInput = document.getElementById('edit-channel-id');
|
||
const nameInput = document.getElementById('edit-channel-name');
|
||
const typeSelect = document.getElementById('edit-channel-type');
|
||
const iconSelect = document.getElementById('edit-channel-icon');
|
||
const categorySelect = document.getElementById('edit-channel-category-id');
|
||
|
||
if (idInput) idInput.value = btn.dataset.id || '';
|
||
if (nameInput) nameInput.value = btn.dataset.name || '';
|
||
if (typeSelect) typeSelect.value = btn.dataset.type || 'chat';
|
||
if (iconSelect) iconSelect.value = btn.dataset.icon || '';
|
||
if (categorySelect) categorySelect.value = btn.dataset.category || '';
|
||
|
||
// Force switch to Overview tab
|
||
const overviewTabBtn = modal.querySelector('[data-bs-target="#edit-channel-general"]');
|
||
if (overviewTabBtn && typeof bootstrap !== 'undefined') {
|
||
bootstrap.Tab.getOrCreateInstance(overviewTabBtn).show();
|
||
}
|
||
|
||
// Also fill delete ID
|
||
const deleteIdInput = document.getElementById('delete-channel-id');
|
||
if (deleteIdInput) deleteIdInput.value = btn.dataset.id || '';
|
||
}
|
||
setTimeout(() => updatePrefix(editChannelType, editChannelIcon, editChannelPrefix), 100);
|
||
}
|
||
});
|
||
|
||
// SortableJS Implementation for Channels
|
||
<?php if ($can_manage_channels): ?>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Sortable for groups (channels inside categories)
|
||
const groups = document.querySelectorAll('.category-group');
|
||
groups.forEach(group => {
|
||
new Sortable(group, {
|
||
group: 'channels',
|
||
draggable: '.channel-item-container',
|
||
animation: 150,
|
||
ghostClass: 'sortable-ghost',
|
||
onEnd: function() {
|
||
saveChannelOrders();
|
||
}
|
||
});
|
||
});
|
||
|
||
// 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',
|
||
onEnd: function() {
|
||
saveChannelOrders();
|
||
}
|
||
});
|
||
});
|
||
|
||
async function saveChannelOrders() {
|
||
const orders = [];
|
||
let position = 0;
|
||
|
||
const sidebar = document.getElementById('sidebar-channels-list');
|
||
|
||
// Iterate over top-level items
|
||
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
|
||
orders.push({
|
||
id: itemId,
|
||
position: position++,
|
||
category_id: null
|
||
});
|
||
|
||
// Now add all channels inside this category
|
||
const subChannels = item.querySelectorAll('.category-group .channel-item-container');
|
||
subChannels.forEach(sub => {
|
||
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 or separator
|
||
orders.push({
|
||
id: itemId,
|
||
position: position++,
|
||
category_id: null
|
||
});
|
||
}
|
||
});
|
||
|
||
try {
|
||
const resp = await fetch('api_v1_channels.php', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
action: 'reorder',
|
||
server_id: "<?php echo $active_server_id; ?>",
|
||
orders: orders
|
||
})
|
||
});
|
||
const data = await resp.json();
|
||
if (!data.success) {
|
||
console.error('Failed to save channel order:', data.error);
|
||
}
|
||
} catch (e) {
|
||
console.error('Error saving channel order:', e);
|
||
}
|
||
}
|
||
<?php endif; ?>
|
||
</script>
|
||
</body>
|
||
</html>
|