1520 lines
100 KiB
PHP
1520 lines
100 KiB
PHP
<?php
|
||
require_once 'auth/session.php';
|
||
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]);
|
||
$channels = $stmt->fetchAll();
|
||
$active_channel_id = $_GET['channel_id'] ?? ($channels[0]['id'] ?? 1);
|
||
|
||
// Fetch active channel details for theme
|
||
$active_channel = null;
|
||
foreach($channels as $c) {
|
||
if($c['id'] == $active_channel_id) {
|
||
$active_channel = $c;
|
||
break;
|
||
}
|
||
}
|
||
|
||
require_once 'includes/permissions.php';
|
||
$is_owner = false;
|
||
$can_manage_channels = false;
|
||
$can_manage_server = false;
|
||
foreach($servers as $s) {
|
||
if($s['id'] == $active_server_id) {
|
||
$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_channel['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
|
||
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_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 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_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
|
||
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_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 r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color
|
||
FROM users u
|
||
JOIN server_members sm ON u.id = sm.user_id
|
||
WHERE sm.server_id = ?
|
||
");
|
||
$stmt->execute([$active_server_id, $active_server_id]);
|
||
$members = $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.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; ?>
|
||
</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 echo !empty($s['icon_url']) ? "background-image: url('{$s['icon_url']}'); background-size: cover;" : ""; ?>">
|
||
<?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
|
||
// Separate categories and channels
|
||
$categories = array_filter($channels, function($c) { return $c['type'] === 'category'; });
|
||
$top_level_channels = array_filter($channels, function($c) use ($channels) {
|
||
return $c['type'] !== 'category' && (empty($c['category_id']) || !in_array($c['category_id'], array_column($channels, 'id')));
|
||
});
|
||
|
||
// Helper to render a channel item
|
||
function renderChannelItem($c, $active_channel_id, $active_server_id, $can_manage_channels) {
|
||
?>
|
||
<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 custom-channel-icon"><i class="fa-solid <?php echo htmlspecialchars($c['icon']); ?>"></i></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-category="<?php echo $c['category_id'] ?? ''; ?>"
|
||
data-theme="<?php echo $c['theme_color']; ?>">
|
||
<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
|
||
}
|
||
|
||
// Render top level channels
|
||
if (!empty($top_level_channels)) {
|
||
foreach($top_level_channels as $c) {
|
||
renderChannelItem($c, $active_channel_id, $active_server_id, $can_manage_channels);
|
||
}
|
||
}
|
||
|
||
// Render categories and their channels
|
||
foreach($categories as $cat) {
|
||
?>
|
||
<div class="category-wrapper" data-id="<?php echo $cat['id']; ?>">
|
||
<div class="channel-category d-flex align-items-center mt-3" data-id="<?php echo $cat['id']; ?>">
|
||
<span class="category-name flex-grow-1 text-uppercase fw-bold" style="font-size: 0.7em; cursor: pointer; color: var(--text-muted);"><?php echo htmlspecialchars($cat['name']); ?></span>
|
||
<?php if ($can_manage_channels): ?>
|
||
<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 $cat['id']; ?>"
|
||
data-name="<?php echo htmlspecialchars($cat['name']); ?>"
|
||
data-type="category"
|
||
data-files="0"
|
||
data-limit="0"
|
||
data-status=""
|
||
data-icon=""
|
||
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 $cat['id']; ?>">+</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="category-group" data-category-id="<?php echo $cat['id']; ?>">
|
||
<?php
|
||
foreach($channels as $c) {
|
||
if ($c['type'] !== 'category' && $c['category_id'] == $cat['id']) {
|
||
renderChannelItem($c, $active_channel_id, $active_server_id, $can_manage_channels);
|
||
}
|
||
}
|
||
?>
|
||
</div>
|
||
</div>
|
||
<?php
|
||
}
|
||
?>
|
||
<?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 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: 20px; 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>';
|
||
}
|
||
?>
|
||
</span>
|
||
<?php if (!$is_dm_view && !empty($active_channel['icon'])): ?>
|
||
<span class="me-2 custom-channel-icon"><i class="fa-solid <?php echo htmlspecialchars($active_channel['icon']); ?>"></i></span>
|
||
<?php endif; ?>
|
||
<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']); ?>
|
||
<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 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">
|
||
<?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): ?>
|
||
<button class="btn btn-primary mt-3" id="add-rule-btn">+ Add a Rule</button>
|
||
<?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 <?php echo htmlspecialchars($thread['username']); ?> • <?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-author" style="<?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
||
<?php echo htmlspecialchars($m['username']); ?>
|
||
<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; ?>
|
||
<?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']; ?>">
|
||
<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>
|
||
<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>
|
||
</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 start-dm-btn" data-user-id="<?php echo $m['id']; ?>" style="color: var(--text-primary); margin-bottom: 8px;">
|
||
<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']); ?>
|
||
</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 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="category">Category (Separator)</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="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">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Channel 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="editChannelTabs" role="tablist">
|
||
<li class="nav-item">
|
||
<button class="nav-link active text-white border-0 bg-transparent" data-bs-toggle="tab" data-bs-target="#edit-channel-general" type="button">General</button>
|
||
</li>
|
||
<li class="nav-item" id="rss-tab-nav" style="display: none;">
|
||
<button class="nav-link text-white border-0 bg-transparent" 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" 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-3">
|
||
<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="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 mb-3">
|
||
<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="category">Category (Separator)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Parent Category</label>
|
||
<select name="category_id" id="edit-channel-category-id" class="form-select bg-dark text-white border-secondary">
|
||
<option value="">No Category (Top level)</option>
|
||
<?php
|
||
foreach($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);">Channel Name</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-dark border-0 text-muted" id="edit-channel-prefix">#</span>
|
||
<input type="text" name="name" id="edit-channel-name" class="form-control" 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" id="edit-channel-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="edit-channel-status-container" style="display: none;">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Channel Status (Voice only)</label>
|
||
<input type="text" name="status" id="edit-channel-status" class="form-control" placeholder="What's happening?">
|
||
<div class="form-text text-muted" style="font-size: 0.8em;">Visible below the channel name.</div>
|
||
</div>
|
||
|
||
<div class="form-check form-switch mb-3">
|
||
<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 File Sharing</label>
|
||
<div class="form-text text-muted" style="font-size: 0.8em;">When disabled, users cannot upload files in this channel.</div>
|
||
</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" id="edit-channel-limit" class="form-control" placeholder="No limit">
|
||
<div class="form-text text-muted" style="font-size: 0.8em;">Keep only the most recent messages.</div>
|
||
</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" id="edit-channel-theme" class="form-control form-control-color w-100" value="#5865f2" title="Choose channel theme color">
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary w-100 mb-2">Save Changes</button>
|
||
</form>
|
||
<button type="button" id="clear-channel-history-btn" class="btn btn-warning w-100 mb-2">Vider l'historique</button>
|
||
<form action="api_v1_channels.php" method="POST" onsubmit="return confirm('Are you sure you want to delete this channel?');">
|
||
<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 w-100">Delete Channel</button>
|
||
</form>
|
||
</div>
|
||
<div class="tab-pane fade" id="edit-channel-rss">
|
||
<div class="mb-3">
|
||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Add New 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/rss.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 Feeds</label>
|
||
<button class="btn btn-sm btn-outline-info" id="sync-rss-btn">Sync Now</button>
|
||
</div>
|
||
<div id="rss-feeds-list" class="list-group list-group-flush bg-transparent">
|
||
<!-- RSS feeds loaded here -->
|
||
</div>
|
||
</div>
|
||
<div class="tab-pane fade" id="edit-channel-permissions">
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<h6 class="mb-0">Role Overrides</h6>
|
||
<div class="dropdown">
|
||
<button class="btn btn-sm btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">Add Role</button>
|
||
<ul class="dropdown-menu dropdown-menu-dark" id="add-permission-role-list">
|
||
<!-- Server roles loaded here -->
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div id="channel-permissions-list" class="list-group list-group-flush bg-transparent">
|
||
<!-- Channel permissions loaded here -->
|
||
</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);">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>
|
||
|
||
<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>';
|
||
return '#';
|
||
}
|
||
|
||
function updatePrefix(typeSelect, iconSelect, prefixSpan) {
|
||
if (!prefixSpan || !typeSelect) return;
|
||
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 || '';
|
||
|
||
// 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',
|
||
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, {
|
||
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 => {
|
||
if (item.classList.contains('category-wrapper')) {
|
||
// It's a category
|
||
const catId = item.dataset.id;
|
||
orders.push({
|
||
id: catId,
|
||
position: position++,
|
||
category_id: null
|
||
});
|
||
|
||
// Now add all channels inside this category
|
||
const subChannels = item.querySelectorAll('.category-group .channel-item-container');
|
||
subChannels.forEach(sub => {
|
||
orders.push({
|
||
id: sub.dataset.id,
|
||
position: position++,
|
||
category_id: catId
|
||
});
|
||
});
|
||
} else if (item.classList.contains('channel-item-container')) {
|
||
// It's a top level channel
|
||
orders.push({
|
||
id: item.dataset.id,
|
||
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>
|