v12
This commit is contained in:
parent
40f605d106
commit
5d6fd46690
@ -17,6 +17,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Handle JSON input
|
||||
$json = json_decode(file_get_contents('php://input'), true);
|
||||
if ($json) {
|
||||
$action = $json['action'] ?? '';
|
||||
if ($action === 'reorder') {
|
||||
$server_id = $json['server_id'] ?? 0;
|
||||
$orders = $json['orders'] ?? []; // Array of {id, position, category_id}
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS)) {
|
||||
$stmt = db()->prepare("UPDATE channels SET position = ?, category_id = ? WHERE id = ? AND server_id = ?");
|
||||
foreach ($orders as $o) {
|
||||
$stmt->execute([$o['position'], $o['category_id'] ?: null, $o['id'], $server_id]);
|
||||
}
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Permission denied']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$action = $_POST['action'] ?? 'create';
|
||||
$server_id = $_POST['server_id'] ?? 0;
|
||||
$user_id = $_SESSION['user_id'];
|
||||
@ -30,6 +52,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$message_limit = !empty($_POST['message_limit']) ? (int)$_POST['message_limit'] : null;
|
||||
$theme_color = $_POST['theme_color'] ?? null;
|
||||
if ($theme_color === '') $theme_color = null;
|
||||
$icon = $_POST['icon'] ?? null;
|
||||
if ($icon === '') $icon = null;
|
||||
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
|
||||
|
||||
// Check if user has permission to manage channels
|
||||
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
|
||||
@ -38,8 +63,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
if ($chan && Permissions::hasPermission($user_id, $chan['server_id'], Permissions::MANAGE_CHANNELS)) {
|
||||
$name = strtolower(preg_replace('/[^a-zA-Z0-9\-]/', '-', $name));
|
||||
$stmt = db()->prepare("UPDATE channels SET name = ?, type = ?, status = ?, allow_file_sharing = ?, theme_color = ?, message_limit = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $type, $status, $allow_file_sharing, $theme_color, $message_limit, $channel_id]);
|
||||
$stmt = db()->prepare("UPDATE channels SET name = ?, type = ?, status = ?, allow_file_sharing = ?, theme_color = ?, message_limit = ?, icon = ?, category_id = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $type, $status, $allow_file_sharing, $theme_color, $message_limit, $icon, $category_id, $channel_id]);
|
||||
}
|
||||
header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id);
|
||||
exit;
|
||||
@ -72,9 +97,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$message_limit = !empty($_POST['message_limit']) ? (int)$_POST['message_limit'] : null;
|
||||
$theme_color = $_POST['theme_color'] ?? null;
|
||||
if ($theme_color === '') $theme_color = null;
|
||||
$icon = $_POST['icon'] ?? null;
|
||||
if ($icon === '') $icon = null;
|
||||
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
|
||||
|
||||
$stmt = db()->prepare("INSERT INTO channels (server_id, name, type, allow_file_sharing, theme_color, message_limit) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$server_id, $name, $type, $allow_file_sharing, $theme_color, $message_limit]);
|
||||
$stmt = db()->prepare("INSERT INTO channels (server_id, name, type, allow_file_sharing, theme_color, message_limit, icon, category_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$server_id, $name, $type, $allow_file_sharing, $theme_color, $message_limit, $icon, $category_id]);
|
||||
$channel_id = db()->lastInsertId();
|
||||
|
||||
header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id);
|
||||
|
||||
@ -178,17 +178,6 @@ body {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.channel-item::before {
|
||||
content: "#";
|
||||
font-size: 1.2em;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.voice-item::before {
|
||||
content: "🔊";
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.server-icon.add-btn {
|
||||
color: #23a559;
|
||||
}
|
||||
@ -203,13 +192,48 @@ body {
|
||||
font-size: 0.75em;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 8px;
|
||||
margin-bottom: 4px;
|
||||
padding: 16px 8px 4px 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.channel-category .channel-settings-btn,
|
||||
.channel-category .add-channel-btn {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
font-size: 1.2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.channel-category:hover .channel-settings-btn,
|
||||
.channel-category:hover .add-channel-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.channel-item-container .channel-settings-btn {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.channel-item-container:hover .channel-settings-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.category-group .channel-item-container {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.category-group {
|
||||
min-height: 5px;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
background-color: var(--hover) !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.add-channel-btn {
|
||||
cursor: pointer;
|
||||
font-size: 1.2em;
|
||||
@ -400,7 +424,27 @@ body {
|
||||
width: 240px;
|
||||
background-color: var(--bg-members);
|
||||
padding: 24px 8px;
|
||||
display: none; /* Hidden on mobile/small screens */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.members-sidebar.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.members-sidebar {
|
||||
display: none;
|
||||
}
|
||||
.members-sidebar.show {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 48px;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
box-shadow: -2px 0 10px rgba(0,0,0,0.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reactions */
|
||||
|
||||
@ -53,18 +53,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
new Notification(`Mention in #${window.currentChannelName}`, {
|
||||
body: `${data.username}: ${data.content}`,
|
||||
icon: data.avatar_url || ''
|
||||
if (e.target.classList.contains('move-rule-btn')) {
|
||||
const id = e.target.dataset.id;
|
||||
const dir = e.target.dataset.dir;
|
||||
const resp = await fetch('api_v1_rules.php', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, dir })
|
||||
});
|
||||
if ((await resp.json()).success) location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -594,10 +583,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
document.addEventListener('click', async (e) => {
|
||||
if (!e.target.closest('.search-container')) {
|
||||
searchResults.style.display = 'none';
|
||||
}
|
||||
|
||||
if (e.target.classList.contains('move-rule-btn')) {
|
||||
const id = e.target.dataset.id;
|
||||
const dir = e.target.dataset.dir;
|
||||
const resp = await fetch('api_v1_rules.php', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, dir })
|
||||
});
|
||||
if ((await resp.json()).success) location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
// Roles Management
|
||||
@ -615,6 +615,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
modal.querySelector('#edit-channel-limit').value = btn.dataset.limit || '';
|
||||
modal.querySelector('#edit-channel-status').value = btn.dataset.status || '';
|
||||
modal.querySelector('#edit-channel-theme').value = btn.dataset.theme || '#5865f2';
|
||||
modal.querySelector('#edit-channel-icon').value = btn.dataset.icon || '';
|
||||
modal.querySelector('#delete-channel-id').value = channelId;
|
||||
|
||||
// Show/Hide RSS tab
|
||||
@ -1348,6 +1349,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
} catch (e) { console.error(e); }
|
||||
});
|
||||
|
||||
// Toggle members sidebar
|
||||
const toggleMembersBtn = document.getElementById('toggle-members-btn');
|
||||
const membersSidebar = document.querySelector('.members-sidebar');
|
||||
if (toggleMembersBtn && membersSidebar) {
|
||||
toggleMembersBtn.addEventListener('click', () => {
|
||||
if (window.innerWidth > 992) {
|
||||
membersSidebar.classList.toggle('hidden');
|
||||
} else {
|
||||
membersSidebar.classList.toggle('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// User Settings - Save
|
||||
const saveSettingsBtn = document.getElementById('save-settings-btn');
|
||||
saveSettingsBtn?.addEventListener('click', async () => {
|
||||
|
||||
BIN
assets/pasted-20260215-151928-c94822be.png
Normal file
BIN
assets/pasted-20260215-151928-c94822be.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
BIN
assets/pasted-20260215-153522-763a8478.png
Normal file
BIN
assets/pasted-20260215-153522-763a8478.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 240 KiB |
@ -9,6 +9,11 @@ class Permissions {
|
||||
const ADMINISTRATOR = 32;
|
||||
|
||||
public static function hasPermission($user_id, $server_id, $permission) {
|
||||
$stmt = db()->prepare("SELECT is_admin FROM users WHERE id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$user = $stmt->fetch();
|
||||
if ($user && $user['is_admin']) return true;
|
||||
|
||||
$stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?");
|
||||
$stmt->execute([$server_id]);
|
||||
$server = $stmt->fetch();
|
||||
|
||||
395
index.php
395
index.php
@ -63,7 +63,7 @@ if ($is_dm_view) {
|
||||
$active_server_id = $_GET['server_id'] ?? ($servers[0]['id'] ?? 1);
|
||||
|
||||
// Fetch channels
|
||||
$stmt = db()->prepare("SELECT * FROM channels WHERE server_id = ?");
|
||||
$stmt = db()->prepare("SELECT * FROM channels WHERE server_id = ? ORDER BY position ASC, id ASC");
|
||||
$stmt->execute([$active_server_id]);
|
||||
$channels = $stmt->fetchAll();
|
||||
$active_channel_id = $_GET['channel_id'] ?? ($channels[0]['id'] ?? 1);
|
||||
@ -192,8 +192,10 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<?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; ?>";
|
||||
@ -266,7 +268,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="channels-list">
|
||||
<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']; ?>"
|
||||
@ -278,25 +280,37 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="channel-category">
|
||||
<span>Text Channels</span>
|
||||
<?php if ($can_manage_channels): ?>
|
||||
<span class="add-channel-btn" title="Create Channel" data-bs-toggle="modal" data-bs-target="#addChannelModal" data-type="chat">+</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php foreach($channels as $c): if($c['type'] === 'voice') continue; ?>
|
||||
<div class="channel-item-container d-flex align-items-center">
|
||||
<?php
|
||||
// Separate categories and channels
|
||||
$categories = array_filter($channels, function($c) { return $c['type'] === 'category'; });
|
||||
$top_level_channels = array_filter($channels, function($c) {
|
||||
return $c['type'] !== 'category' && (empty($c['category_id']) || !in_array($c['category_id'], array_column($GLOBALS['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' : ''; ?>">
|
||||
<span class="me-1">
|
||||
<?php
|
||||
if ($c['type'] === 'announcement') echo '📢';
|
||||
elseif ($c['type'] === 'rules') echo '📜';
|
||||
elseif ($c['type'] === 'forum') echo '🏛️';
|
||||
else echo '#';
|
||||
?>
|
||||
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 echo htmlspecialchars($c['name']); ?>
|
||||
<?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);"
|
||||
@ -307,42 +321,59 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
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 endforeach; ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
<div class="channel-category" style="margin-top: 16px;">
|
||||
<span>Voice Channels</span>
|
||||
<?php if ($can_manage_channels): ?>
|
||||
<span class="add-channel-btn" title="Create Channel" data-bs-toggle="modal" data-bs-target="#addChannelModal" data-type="voice">+</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php foreach($channels as $c): if($c['type'] !== 'voice') continue; ?>
|
||||
<div class="channel-item-container d-flex align-items-center">
|
||||
<div class="channel-item voice-item flex-grow-1" data-channel-id="<?php echo $c['id']; ?>">
|
||||
<span>🔊 <?php echo htmlspecialchars($c['name']); ?></span>
|
||||
<?php if (!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>
|
||||
// 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>
|
||||
<?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-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 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 endforeach; ?>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="user-panel">
|
||||
@ -370,10 +401,28 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<!-- Chat Area -->
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<span style="color: var(--text-muted); margin-right: 8px;"><?php echo $is_dm_view ? '@' : '#'; ?></span>
|
||||
<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>
|
||||
@ -674,6 +723,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<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>
|
||||
|
||||
@ -753,7 +803,6 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
@ -947,25 +996,48 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
<form action="api_v1_channels.php" method="POST">
|
||||
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
|
||||
<input type="hidden" name="type" id="channel-type-input" value="chat">
|
||||
<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="channel-type-select">
|
||||
<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="channel-hash-prefix">#</span>
|
||||
<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>
|
||||
@ -975,10 +1047,6 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<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 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="#5865f2" title="Choose channel theme color">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link text-white text-decoration-none" data-bs-dismiss="modal">Cancel</button>
|
||||
@ -1024,12 +1092,54 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<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>
|
||||
<input type="text" name="name" id="edit-channel-name" class="form-control" required>
|
||||
<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;">
|
||||
@ -1219,14 +1329,181 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<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 in modal
|
||||
// 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');
|
||||
document.getElementById('channel-type-input').value = type;
|
||||
document.getElementById('channel-hash-prefix').textContent = type === 'text' ? '#' : '🔊';
|
||||
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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user