emote autoroles
This commit is contained in:
parent
e6233598d6
commit
cd2b57b27d
139
api/emotes.php
Normal file
139
api/emotes.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
if ($action === 'list') {
|
||||
try {
|
||||
$stmt = db()->query("SELECT * FROM custom_emotes ORDER BY created_at DESC");
|
||||
echo json_encode(['success' => true, 'emotes' => $stmt->fetchAll()]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'upload' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!isset($_FILES['emote'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Aucun fichier reçu (emote)']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_FILES['emote']['error'] !== UPLOAD_ERR_OK) {
|
||||
$errorMsg = 'Erreur d\'upload PHP: ' . $_FILES['emote']['error'];
|
||||
if ($_FILES['emote']['error'] === 1) $errorMsg = 'Fichier trop volumineux (limit PHP)';
|
||||
if ($_FILES['emote']['error'] === 4) $errorMsg = 'Aucun fichier sélectionné';
|
||||
echo json_encode(['success' => false, 'error' => $errorMsg]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$name = preg_replace('/[^a-z0-9_]/', '', strtolower($_POST['name'] ?? 'emote'));
|
||||
if (empty($name)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Nom invalide']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$file = $_FILES['emote'];
|
||||
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||
if (!in_array(strtolower($ext), ['png', 'jpg', 'jpeg'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Format non supporté (PNG uniquement recommandé)']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$uploadDir = __DIR__ . '/../assets/images/custom_emotes/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0775, true);
|
||||
}
|
||||
|
||||
$fileName = time() . '_' . $name . '.png';
|
||||
$targetPath = $uploadDir . $fileName;
|
||||
|
||||
// Process image: Resize to 48x48
|
||||
$srcImage = null;
|
||||
if (strtolower($ext) === 'png') $srcImage = imagecreatefrompng($file['tmp_name']);
|
||||
else if (in_array(strtolower($ext), ['jpg', 'jpeg'])) $srcImage = imagecreatefromjpeg($file['tmp_name']);
|
||||
|
||||
if (!$srcImage) {
|
||||
echo json_encode(['success' => false, 'error' => 'Fichier image corrompu']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$width = imagesx($srcImage);
|
||||
$height = imagesy($srcImage);
|
||||
$newSize = 48;
|
||||
$dstImage = imagecreatetruecolor($newSize, $newSize);
|
||||
|
||||
// Transparency for PNG
|
||||
imagealphablending($dstImage, false);
|
||||
imagesavealpha($dstImage, true);
|
||||
$transparent = imagecolorallocatealpha($dstImage, 255, 255, 255, 127);
|
||||
imagefilledrectangle($dstImage, 0, 0, $newSize, $newSize, $transparent);
|
||||
|
||||
imagecopyresampled($dstImage, $srcImage, 0, 0, 0, 0, $newSize, $newSize, $width, $height);
|
||||
imagepng($dstImage, $targetPath);
|
||||
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($dstImage);
|
||||
|
||||
$relativePath = 'assets/images/custom_emotes/' . $fileName;
|
||||
$code = ':' . $name . ':';
|
||||
|
||||
try {
|
||||
$stmt = db()->prepare("INSERT INTO custom_emotes (name, path, code) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$name, $relativePath, $code]);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'emote' => [
|
||||
'id' => db()->lastInsertId(),
|
||||
'name' => $name,
|
||||
'path' => $relativePath,
|
||||
'code' => $code
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'rename' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$id = $_POST['id'] ?? 0;
|
||||
$newName = preg_replace('/[^a-z0-9_]/', '', strtolower($_POST['name'] ?? ''));
|
||||
if (empty($newName)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Nom invalide']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$code = ':' . $newName . ':';
|
||||
$stmt = db()->prepare("UPDATE custom_emotes SET name = ?, code = ? WHERE id = ?");
|
||||
$stmt->execute([$newName, $code, $id]);
|
||||
echo json_encode(['success' => true, 'name' => $newName, 'code' => $code]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'delete' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$id = $_POST['id'] ?? 0;
|
||||
try {
|
||||
$stmt = db()->prepare("SELECT path FROM custom_emotes WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$emote = $stmt->fetch();
|
||||
if ($emote) {
|
||||
$filePath = __DIR__ . '/../' . $emote['path'];
|
||||
if (file_exists($filePath)) unlink($filePath);
|
||||
|
||||
$stmt = db()->prepare("DELETE FROM custom_emotes WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Emote non trouvée']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
@ -872,6 +872,48 @@ body {
|
||||
border-left: 2px solid var(--blurple);
|
||||
}
|
||||
|
||||
/* Emotes Tab in Settings */
|
||||
#settings-emotes-sidebar button {
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
color: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
#settings-emotes-sidebar button:hover {
|
||||
background-color: var(--hover) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
#settings-emotes-sidebar button.active {
|
||||
background-color: var(--active) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.role-emoji-item {
|
||||
transition: transform 0.1s, background-color 0.2s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.role-emoji-item:hover {
|
||||
transform: scale(1.2);
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
border-color: var(--blurple);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.role-emoji-item img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
#custom-emote-upload-zone .btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pinned-badge {
|
||||
font-size: 0.7em;
|
||||
color: var(--blurple);
|
||||
|
||||
@ -32,30 +32,221 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
'Smileys': ['😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂', '🙂', '🙃', '😉', '😊', '😇', '🥰', '😍', '🤩', '😘', '😗', '😚', '😙', '😋', '😛', '😜', '🤪', '😝', '🤑', '🤗', '🤭', '🤫', '🤔', '🤐', '🤨', '😐', '😑', '😶', '😏', '😒', '🙄', '😬', '🤥', '😌', '😔', '😪', '🤤', '😴', '😷', '🤒', '🤕', '🤢', '🤮', '🤧', '🥵', '🥶', '🥴', '😵', '🤯', '🤠', '🥳', '😎', '🤓', '🧐', '😕', '😟', '🙁', '☹️', '😮', '😯', '😲', '😳', '🥺', '😦', '😧', '😨', '😰', '😥', '😢', '😭', '😱', '😖', '😣', '😞', '😓', '😩', '😫', '🥱', '😤', '😡', '😠', '🤬', '😈', '👿', '👹', '👺', '💀', '☠️', '💩', '🤡', '👻', '👽', '👾', '🤖', '😺', '😸', '😻', '😼', '😽', '🙀', '😿', '😾', '🙈', '🙉', '🙊', '💋', '💌', '💘', '💝', '💖', '💗', '💓', '💞', '💕', '💟', '❣️', '💔', '❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💯', '💢', '💥', '💫', '💦', '💨', '🕳️', '💣', '💬', '👁️🗨️', '🗨️', '🗯️', '💭', '💤', '🪐', '🌠', '🎇', '🎆', '🌇', '🌆', '🏙️', '🌃', '🌌'],
|
||||
'Gestures': ['👋', '🤚', '🖐️', '✋', '🖖', '👌', '🤏', '✌️', '🤞', '🤟', '🤘', '🤙', '👈', '👉', '👆', '🖕', '👇', '☝️', '👍', '👎', '✊', '👊', '🤛', '🤜', '👏', '🙌', '👐', '🤲', '🤝', '🙏', '✍️', '💅', '🤳', '💪', '🦾', '🦵', '🦿', '🦶', '👂', '🦻', '👃', '🧠', '🦷', '🦴', '👀', '👁️', '👅', '👄', '🖖', '🤘', '🤙', '🖐️', '🖕', '🖖', '✍️', '🤳', '💪', '🦾'],
|
||||
'People': ['👶', '🧒', '👦', '👧', '🧑', '👱', '👨', '👩', '🧓', '👴', '👵', '👮', '🕵️', '💂', '👷', '🤴', '👸', '👳', '👲', '🧕', '🤵', '👰', '🤰', '🤱', '👼', '🎅', '🤶', '🦸', '🦹', '🧙', '🧚', '🧛', '🧜', '🧝', '🧞', '🧟', '💆', '💇', '🚶', '🏃', '💃', '🕺', '🕴️', '👯', '🧖', '🧗', '🤺', '🏇', '⛷️', '🏂', '🏌️', '🏄', '🚣', '🏊', '⛹️', '🏋️', '🚴', '🚵', '🤸', '🤼', '🤽', '🤾', '🤹', '🧘', '🛀', '🛌'],
|
||||
'Animals': ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🐯', '🦁', '🐮', '🐷', '🐽', '🐸', '🐵', '🙈', '🙉', '🙊', '🐒', '🐔', '🐧', '🐦', '🐤', '🐣', '🐥', '🦆', '🦅', '🦉', '🦇', '🐺', '🐗', '🐴', '🦄', '🐝', '🐛', '🦋', '🐌', '🐞', '🐜', '🦟', '🦗', '🕷️', '🕸️', '🦂', '🐢', '🐍', '🦎', '🦖', '🦕', '🐙', '🦑', '🦐', '🦞', '🦀', '🐡', '🐠', '🐟', '🐬', '🐳', '🐋', '🦈', '🐊', '🐅', '🐆', '🦓', '🦍', '🦧', '🐘', '🦛', '🦏', '🐪', '🐫', '🦒', '🦘', '🦬', '🐃', '🐂', '🐄', '🐎', '🐖', '🐏', '🐑', '🐐', '🦌', '🐕', '🐩', '🦮', '🐕🦺', '🐈', '🐈⬛', '🐓', '🦃', '🦚', '🦜', '🦢', '🦩', '🕊️', '🐇', '🦝', '🦨', '🦡', '🦦', '🦥', '🐁', '🐀', '🐿️', '🦔', '🐾', '🐉', '🐲', '🌵', '🎄', '🌲', '🌳', '🌴', '🌱', '🌿', '☘️', '🍀', '🎍', '🎋', '🍃', '🍂', '🍁', '🍄', '🐚', '🌾'],
|
||||
'Animals': ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🐯', '🦁', '🐮', '🐷', '🐽', '🐸', '🐵', '🙈', '🙉', '🙊', '🐒', '🐔', '🐧', '🐦', '🐤', '🐣', '🐥', '🦆', '🦅', '🦉', '🦇', '🐺', '🐗', '🐴', '🦄', '🐝', '🐛', '🦋', '🐌', '🐞', '🐜', '🦟', '🦗', '🕷️', '🕸️', '蠍', '🐢', '🐍', '🦎', '🦖', '🦕', '🐙', '🦑', '🦐', '🦞', '🦀', '🐡', '🐠', '🐟', '🐬', '🐳', '🐋', '🦈', '🐊', '🐅', '🐆', '🦓', '🦍', '🦧', '🐘', '🦛', '🦏', '🐪', '🐫', '🦒', '🦘', '🦬', '🐃', '🐂', '🐄', '🐎', '🐖', '🐏', '🐑', '🐐', '🦌', '🐕', '🐩', '🦮', '🐕🦺', '🐈', '🐈⬛', '🐓', '🦃', '🦚', '🦜', '🦢', '🦩', '🕊️', '🐇', '🦝', '🦨', '🦡', '🦦', '🦥', '🐁', '🐀', '🐿️', '🦔', '🐾', '🐉', '🐲', '🌵', '🎄', '🌲', '🌳', '🌴', '🌱', '🌿', '☘️', '🍀', '🎍', '🎋', '🍃', '🍂', '🍁', '🍄', '🐚', '🌾'],
|
||||
'Nature': ['💐', '🌷', '🌹', '🥀', '🌺', '🌸', '🌼', '🌻', '🌞', '🌝', '🌛', '🌜', '🌚', '🌕', '🌖', '🌗', '🌘', '🌑', '🌒', '🌓', '🌔', '🌙', '🌎', '🌍', '🌏', '🪐', '💫', '⭐️', '🌟', '✨', '⚡️', '☄️', '💥', '🔥', '🌪️', '🌈', '☀️', '🌤️', '⛅️', '🌥️', '☁️', '🌦️', '🌧️', '🌨️', '🌩️', '❄️', '☃️', '⛄️', '🌬️', '💨', '💧', '💦', '☔️', '☂️', '🌊', '🌫️', '⛰️', '🏔️', '🗻', '🌋', '🏜️', '🏖️', '🏝️', '🏕️', '⛺️'],
|
||||
'Food': ['🍏', '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🍈', '🍒', '🍑', '🥭', '🍍', '🥥', '🥝', '🍅', '🍆', '🥑', '🥦', '🥬', '🥒', '🌽', '🥕', '🧄', '🧅', '🍄', '🥜', '🌰', '🍞', '🥐', '🥖', '🥨', '🥯', '🥞', '🧇', '🧀', '🍖', '🍗', '🥩', '🥓', '🍔', '🍟', '🍕', '🌭', '🥪', '🌮', '🌯', '🥙', '🧆', '🍳', '🥘', '🍲', '🥣', '🥗', '🍿', '🧈', '🧂', '🥫', '🍱', '🍘', '🍙', '🍚', '🍛', '🍜', '🍝', '🍠', '🍢', '🍣', '🍤', '🍥', '🥮', '🍡', '🥟', '🥠', '🥡', '🍦', '🍧', '🍨', '🍩', '🍪', '🎂', '🍰', '🧁', '🥧', '🍫', '🍬', '🍭', '🍮', '🍯', '🍼', '🥛', '☕️', '🍵', '🧉', '🥤', '🧃', '🍺', '🍻', '🥂', '🍷', '🥃', '🍸', '🍹', '🍾', '🧊', '🥄', '🍴', '🍽️'],
|
||||
'Activities': ['⚽️', '🏀', '🏈', '⚾️', '🥎', '🎾', '🏐', '🏉', '🎱', '🏓', '🏸', '🥅', '🏒', '🏑', '🏏', '⛳️', '🏹', '🎣', '🥊', '🥋', '🛹', '🛷', '⛸️', '🥌', '🎿', '⛷️', '🏂', '🏋️', '🤺', '🤼', '🤸', '⛹️', '🤽', '🤾', '🤹', '🧘', '🏇', '🚣', '🏊', '🚴', '🚵', '🧗', '🎖️', '🏆', '🏅', '🥇', '🥈', '🥉', '🎫', '🎟️', '🎭', '🎨', '🎬', '🎤', '🎧', '🎼', '🎹', '🥁', '🎷', '🎺', '🎸', '🪕', '🎻', '🎲', '♟️', '🎯', '🎳', '🎮', '🎰', '🧩'],
|
||||
'Travel': ['🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐', '🚚', '🚛', '🚜', '🛵', '🚲', '🛴', '🚏', '🛣️', '🛤️', '⛽️', '🚨', '🚥', '🚦', '🚧', '⚓️', '⛵️', '🚤', '🛳️', '⛴️', '🚢', '✈️', '🛫', '🛬', '💺', '🚁', '🚟', '🚠', '🚡', '🚀', '🛸', '🛰️', '⌛️', '⏳', '⌚️', '⏰', '⏱️', '⏲️', '🕰️', '🌡️', '🌃', '🏙️', '🌄', '🌅', '🌆', '🌇', '🌉', '🎠', '🎡', '🎢', '🚂', '🚃', '🚄', '🚅', '🚆', '🚇', '🚈', '🚉', '🚊', '🚝', '🚞', '🚋'],
|
||||
'Objects': ['⌚️', '📱', '📲', '💻', '⌨️', '🖱️', '🖲️', '🕹️', '🗜️', '💽', '💾', '💿', '📀', '📼', '📷', '📸', '📹', '🎥', '📽️', '🎞️', '📞', '📠', '📺', '📻', '🎙️', '🎚️', '🎛️', '🧭', '⏱️', '⏲️', '⏰', '🕰️', '⌛️', '⏳', '📡', '🔋', '🔌', '💡', '🔦', '🕯️', '🪔', '🧯', '🛢️', '💸', '💵', '💴', '💶', '💷', '💰', '💳', '💎', '⚖️', '🧰', '🔧', '🔨', '⚒️', '🛠️', '⛏️', '🔩', '⚙️', '🧱', '⛓️', '🧲', '🔫', '💣', '🧨', '🪓', '🔪', '🗡️', '⚔️', '🛡️', '🚬', '⚰️', '⚱️', '🏺', '🔮', '🧿', '📿', '💈', '⚗️', '🔭', '🔬', '🕳️', '💊', '💉', '🩸', '🧬', '🦠', '🧫', '🧪', '🌡️', '🧹', '🧺', '🧻', '🧼', '🧽', '🪒', '🧴', '🛎️', '🔑', '🗝️', '🚪', '🪑', '🛋️', '🛏️', '🛌', '🧸', '🖼️', '🛍️', '🛒', '🎁', '🎈', '🎏', '🎀', '🎊', '🎉', '🎎', '🏮', '🎐', '🧧', '✉️', '📩', '📨', '📧', '💌', '📥', '📤', '📦', '🏷️', '📁', '📂', '📅', '📆', '🗒️', '🗓️', '📇', '📈', '📉', '📊', '📋', '📌', '📍', '📎', '🖇️', '📏', '📐', '✂️', '🗃️', '🗄️', '🗑️', '🔒', '🔓', '🔏', '🔐', '🔑', '🗝️'],
|
||||
'Symbols': ['💘', '💝', '💖', '💗', '💓', '💞', '💕', '💟', '❣️', '💔', '❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💯', '💢', '💥', '💫', '💦', '💨', '🕳️', '💣', '💬', '👁️🗨️', '🗨️', '🗯️', '💭', '💤', '🌐', '♠️', '♥️', '♦️', '♣️', '🃏', '🀄️', '🎴', '🔇', '🔈', '🔉', '🔊', '📢', '📣', '📯', '🔔', '🔕', '🎼', '🎵', '🎶', '💹', '🏧', '🚮', '🚰', '♿️', '🚹', '🚺', '🚻', '🚼', '🚾', '🛂', '🛃', '🛄', '🛅', '⚠️', '🚸', '⛔️', '🚫', '🚳', '🚭', '🚯', '🚱', '🚷', '📵', '🔞', '☢️', '☣️', '⬆️', '↗️', '➡️', '↘️', '⬇️', '↙️', '⬅️', '↖️', '↕️', '↔️', '↩️', '↪️', '⤴️', '⤵️', '🔃', '🔄', '🔙', '🔚', '🔛', '🔜', '🔝', '🛐', '⚛️', '🕉️', '✡️', '☸️', '☯️', '✝️', '☦️', '☪️', '☮️', '🕎', '🔯', '♈️', '♉️', '♊️', '♋️', '♌️', '♍️', '♎️', '♏️', '♐️', '♑️', '♒️', '♓️', '⛎', '🔀', '🔁', '🔂', '▶️', '⏩', '⏭️', '⏯️', '◀️', '⏪', '⏮️', '🔼', '⏫', '🔽', '⏬', '⏸️', '⏹️', '⏺️', '⏏️', '🎦', '🔅', '🔆', '📶', '📳', '📴', '➕', '➖', '➗', '✖️', '♾️', '‼️', '⁉️', '❓', '❔', '❕', '❗️', '〰️', '💱', '💲', '⚕️', '♻️', '⚜️', '🔱', '📛', '🔰', '⭕️', '✅', '☑️', '✔️', '✖️', '❌', '❎', '➰', '➿', '〽️', '✳️', '✴️', '❇️', '‼️', '🈁', '🈂️', '🈷️', '🈶', '🈯️', '🉐', '🈹', '🈚️', '🈲', '🉑', '🈸', '🈴', '🈳', '㊗️', '㊙️', '🈺', '🈵', '🔴', '🟠', '🟡', '🟢', '🔵', '🟣', '🟤', '⚫️', '⚪️', '🟥', '🟧', '🟨', '🟩', '🟦', '🟪', '🟫', '⬛️', '⬜️', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '⛎', '☸', '☦', '☦', '☪', '☮', '☯', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '⛎', '♀', '♂', '⚕', '♾', '⚓', '⚔', '⚖', '⚗', '⚙', '⚖', '⚓', '⚔'],
|
||||
'Food': ['🍏', '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🍈', '🍒', '🍑', '🥭', '🍍', '🥥', '🥝', '🍅', '🍆', '🥑', '🥦', '🥬', '🥒', '🌽', '🥕', '🧄', '🧅', '🍄', '🥜', '🌰', '🍞', '🥐', '🥖', '🥨', '🥯', '🥞', '🧇', '🧀', '🍖', '🍗', '🥩', '🥓', '🍔', '🍟', '🍕', '🌭', '🥪', '🌮', '🌯', '🥙', '🧆', '🍳', '🥘', '🍲', '🥣', '🥗', '🍿', 'バター', '🧂', '🥫', '🍱', '🍘', '🍙', '🍚', '🍛', '🍜', '🍝', '🍠', '🍢', '🍣', '🍤', '🍥', '🥮', '🍡', '🥟', '🥠', '🥡', '🍦', '🍧', '🍨', '🍩', '🍪', '🎂', '🍰', '🧁', '🥧', '🍫', '🍬', '🍭', '🍮', '🍯', '🍼', '🥛', '☕️', '🍵', '🧉', '🥤', '🧃', '🍺', '🍻', '🥂', '🍷', '🥃', '🍸', '🍹', '🍾', '🧊', '🥄', '🍴', '🍽️'],
|
||||
'Activities': ['⚽️', '🏀', '🏈', '⚾️', '🥎', '🎾', '🏐', '🏉', '🎱', '🏓', '🏸', '🥅', '🏒', '🏑', '🏏', '⛳️', '🏹', '🎣', '🥊', '🥋', '🛹', '🛷', '⛸️', '🥌', '🎿', '⛷️', '🏂', '🏋️', '🤺', '🤼', '🤸', '⛹️', '🤽', '🤾', '🤹', '🧘', '🏇', '🚣', '🏊', '🚴', '🚵', '🧗', '🎖️', '🏆', '🏅', '🥇', '🥈', '🥉', '🎫', '🎟️', '🎭', '🎨', '🎬', '🎤', '🎧', '🎼', '🎹', '🥁', '🎷', '🎺', '🎸', '🪕', '🎻', '🎲', '♟️', '🎯', 'コツ', '🎮', '🎰', '🧩'],
|
||||
'Travel': ['🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐', '🚚', '🚛', '🚜', '🛵', '🚲', '🛴', '🚏', '🛣️', '🛤️', '⛽️', '🚨', '🚥', '🚦', '🚧', '⚓️', '⛵️', '🚤', '🛳️', '⛴️', '🚢', '✈️', '🛫', '🛬', '💺', '🚁', '🚟', 'ケーブル', '🚡', '🚀', '🛸', '🛰️', '⌛️', '⏳', '⌚️', '⏰', '⏱️', '⏲️', '🕰️', '🌡️', '🌃', '🏙️', '🌄', '🌅', '🌆', '🌇', '🌉', '🎠', '🎡', '🎢', '🚂', '🚃', '🚄', '🚅', '🚆', '🚇', '🚈', '🚉', '🚊', '🚝', '🚞', '🚋'],
|
||||
'Objects': ['⌚️', '📱', '📲', '💻', '⌨️', '🖱️', '🖲️', '🕹️', '🗜️', '💽', '💾', '💿', '📀', '📼', '📷', '📸', '📹', '🎥', '📽️', '🎞️', '📞', '📠', '📺', '📻', '🎙️', '🎚️', '🎛️', '🧭', '⏱️', '⏲️', '⏰', '🕰️', '⌛️', '⏳', '📡', '🔋', 'プラグ', '💡', '🔦', '🕯️', '🪔', '🧯', '🛢️', '💸', '💵', '💴', '💶', '💷', '💰', '💳', '💎', '⚖️', '🧰', 'レンチ', '🔨', '⚒️', '🛠️', '⛏️', 'ナット', '⚙️', '🧱', '鎖', '🧲', '🔫', '💣', '🧨', '🪓', 'ナイフ', '🗡️', '⚔️', '盾', '🚬', '⚰️', '⚱️', '🏺', '水晶', '🧿', '📿', '💈', '⚗️', '望遠鏡', '🔬', '🕳️', '💊', '💉', '🩸', 'DNA', '🦠', '🧫', '🧪', '🌡️', '🧹', 'カゴ', '🧻', '石鹸', 'スポンジ', '🪒', 'ローション', '🛎️', '鍵', '🗝️', 'ドア', '椅子', 'ソファ', 'ベッド', '🛌', 'テディベア', '額縁', '袋', 'カート', '🎁', '🎈', '🎏', 'リボン', '🎊', '🎉', '人形', '提灯', '🎐', '🧧', '✉️', '📩', '📨', '📧', '💌', '📥', '📤', '📦', '🏷️', 'フォルダ', '📂', 'カレンダー', '📆', '🗒️', '🗓️', '📇', 'チャート', '📉', '📊', 'クリップボード', '画鋲', '📍', '📎', '🖇️', '定規', '📐', 'ハサミ', '🗃️', 'キャビネット', 'ゴミ箱', '🔒', '🔓', '🔏', '🔐', '鍵', '🗝️'],
|
||||
'Symbols': ['💘', '💝', '💖', '💗', '💓', '💞', '💕', '💟', '❣️', '💔', '❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💯', '💢', '💥', '💫', '💦', '💨', '🕳️', '💣', '💬', '👁️🗨️', '🗨️', '🗯️', '💭', '💤', '🌐', '♠️', '♥️', '♦️', '♣️', 'ジョーカー', '🀄️', '🎴', '🔇', '🔈', '🔉', '🔊', '📢', '📣', '📯', '🔔', '🔕', '🎼', '🎵', '🎶', '💹', 'ATM', '🚮', '🚰', '♿️', '🚹', '🚺', '🚻', '🚼', '🚾', '🛂', 'カスタム', 'バゲージ', '🛅', '⚠️', '🚸', '⛔️', '🚫', '🚳', '🚯', '🚱', '🚷', '📵', '🔞', '放射能', 'バイオ', '⬆️', '↗️', '➡️', '↘️', '⬇️', '↙️', '⬅️', '↖️', '↕️', '↔️', '↩️', '↪️', '⤴️', '⤵️', '🔃', '🔄', '🔙', '🔚', '🔛', '🔜', '🔝', '🛐', '⚛️', '🕉️', '✡️', '☸️', '☯️', '✝️', '☦️', '☪️', '☮️', '🕎', '🔯', '♈️', '♉️', '♊️', '♋️', '♌️', '♍️', '♎️', '♏️', '♐️', '♑️', '♒️', '♓️', '⛎', '🔀', '🔁', '🔂', '▶️', '⏩', '⏭️', '⏯️', '◀️', '⏪', '⏮️', '🔼', '⏫', '🔽', '⏬', '⏸️', '⏹️', '⏺️', '⏏️', '🎦', '🔅', '🔆', '📶', '📳', '📴', '➕', '➖', '➗', '✖️', '♾️', '‼️', '⁉️', '❓', '❔', '❕', '❗️', '〰️', '💱', '💲', '⚕️', '♻️', '⚜️', '🔱', '📛', '🔰', '⭕️', '✅', '☑️', '✔️', '✖️', '❌', '❎', '➰', '➿', '〽️', '✳️', '✴️', '❇️', '‼️', '🈁', '🈂️', '🈷️', '🈶', '🈯️', '🉐', '🈹', '🈚️', '🈲', '🉑', '🈸', '🈴', '🈳', '㊗️', '㊙️', '🈺', '🈵', '🔴', '🟠', '🟡', '🟢', '🔵', '🟣', '🟤', '⚫️', '⚪️', '🟥', '🟧', '🟨', '🟩', '🟦', '🟪', '🟫', '⬛️', '⬜️', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '⛎', '☸', '☦', '☦', '☪', '☮', '☯', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '⛎', '♀', '♂', '⚕', '♾', '⚓', '⚔', '⚖', '⚗', '⚙', '⚖', '⚓', '⚔'],
|
||||
'Flags': ['🏁', '🚩', '🎌', '🏴', '🏳️', '🏳️🌈', '🏳️⚧️', '🏴☠️', '🇦🇫', '🇦🇽', '🇦🇱', '🇩🇿', '🇦🇲', '🇦🇺', '🇦🇹', '🇦🇿', '🇧🇪', '🇧🇷', '🇨🇦', '🇨🇱', '🇨🇳', '🇨🇴', '🇨🇿', '🇩🇰', '🇪🇬', '🇫🇮', '🇫🇷', '🇩🇪', '🇬🇷', '🇭🇰', '🇮🇳', '🇮🇩', '🇮🇪', '🇮🇱', '🇮🇹', '🇯🇵', '🇰🇷', '🇲🇽', '🇳🇱', '🇳🇿', '🇳🇴', '🇵🇰', '🇵🇭', '🇵🇱', '🇵🇹', '🇷🇺', '🇸🇦', '🇸🇬', '🇿🇦', '🇪🇸', '🇸🇪', '🇨🇭', '🇹🇭', '🇹🇷', '🇺🇦', '🇦🇪', '🇬🇧', '🇺🇸', '🇻🇳', '🇦🇷', '🇧🇩', '🇧🇪', '🇧🇴', '🇮🇩', '🇮🇷', '🇮🇶', '🇯🇲', '🇰🇿', '🇰🇪', '🇲🇾', '🇲🇦', '🇳🇬', '🇵🇪', '🇷🇴', '🇷🇸', '🇸🇰', '🇺🇾', '🇿🇼']
|
||||
};
|
||||
|
||||
const categoryIcons = {
|
||||
'Custom': '⭐',
|
||||
'Smileys': '😀',
|
||||
'Gestures': '👌',
|
||||
'People': '👶',
|
||||
'Animals': '🐶',
|
||||
'Nature': '🌵',
|
||||
'Food': '🍏',
|
||||
'Activities': '⚽️',
|
||||
'Travel': '🚗',
|
||||
'Objects': '⌚️',
|
||||
'Symbols': '❤️',
|
||||
'Flags': '🏁'
|
||||
};
|
||||
|
||||
const ALL_EMOJIS = Object.values(EMOJI_CATEGORIES).flat();
|
||||
|
||||
// Unified custom emote loading and caching
|
||||
window.CUSTOM_EMOTES_CACHE = [];
|
||||
window.loadCustomEmotes = async () => {
|
||||
try {
|
||||
const resp = await fetch('api/emotes.php?action=list');
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
window.CUSTOM_EMOTES_CACHE = data.emotes || [];
|
||||
return window.CUSTOM_EMOTES_CACHE;
|
||||
}
|
||||
return [];
|
||||
} catch (e) {
|
||||
console.error("Failed to load custom emotes", e);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// Settings Emotes Tab Logic
|
||||
async function setupSettingsEmotes() {
|
||||
console.log("Setting up Emotes Tab...");
|
||||
const sidebar = document.getElementById('settings-emotes-sidebar');
|
||||
const grid = document.getElementById('settings-emotes-grid');
|
||||
const searchInput = document.getElementById('settings-emotes-search');
|
||||
const uploadZone = document.getElementById('custom-emote-upload-zone');
|
||||
const uploadInput = document.getElementById('emote-upload-input');
|
||||
|
||||
if (!sidebar || !grid) return;
|
||||
|
||||
const categories = ['Custom', ...Object.keys(EMOJI_CATEGORIES)];
|
||||
|
||||
const renderGrid = async (category, searchTerm = '') => {
|
||||
grid.innerHTML = '<div class="col-12 text-center p-4"><div class="spinner-border spinner-border-sm text-primary"></div></div>';
|
||||
|
||||
if (category === 'Custom' && !searchTerm) {
|
||||
if (uploadZone) uploadZone.classList.remove('d-none');
|
||||
const emotes = await window.loadCustomEmotes();
|
||||
grid.innerHTML = '';
|
||||
|
||||
if (emotes.length === 0) {
|
||||
grid.innerHTML = '<div class="col-12 text-center text-muted p-4" style="grid-column: span 8;">Aucune emote personnalisée. Ajoutez-en une !</div>';
|
||||
} else {
|
||||
emotes.forEach(emote => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'role-emoji-item rounded d-flex flex-column align-items-center justify-content-center p-2 text-center position-relative';
|
||||
div.style.cursor = 'pointer';
|
||||
div.style.backgroundColor = 'rgba(255,255,255,0.05)';
|
||||
div.style.minHeight = '70px';
|
||||
div.innerHTML = `
|
||||
<img src="${emote.path}" style="width: 32px; height: 32px; object-fit: contain;">
|
||||
<small class="text-white mt-1" style="font-size: 10px; opacity: 0.7;">${emote.code}</small>
|
||||
<div class="emote-actions position-absolute top-0 end-0 p-1 d-none">
|
||||
<button class="btn btn-sm btn-link text-info p-0 me-1 edit-emote" title="Renommer"><i class="fas fa-edit" style="font-size: 10px;"></i></button>
|
||||
<button class="btn btn-sm btn-link text-danger p-0 delete-emote" title="Supprimer"><i class="fas fa-trash" style="font-size: 10px;"></i></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
div.onmouseenter = () => div.querySelector('.emote-actions')?.classList.remove('d-none');
|
||||
div.onmouseleave = () => div.querySelector('.emote-actions')?.classList.add('d-none');
|
||||
|
||||
div.onclick = (e) => {
|
||||
if (e.target.closest('.emote-actions')) return;
|
||||
navigator.clipboard.writeText(emote.code);
|
||||
const originalBg = div.style.backgroundColor;
|
||||
div.style.backgroundColor = 'var(--blurple)';
|
||||
setTimeout(() => div.style.backgroundColor = originalBg, 200);
|
||||
};
|
||||
|
||||
div.querySelector('.delete-emote').onclick = async (e) => {
|
||||
e.stopPropagation();
|
||||
if (!confirm(`Supprimer l'emote ${emote.code} ?`)) return;
|
||||
const fd = new FormData();
|
||||
fd.append('id', emote.id);
|
||||
const res = await (await fetch('api/emotes.php?action=delete', { method: 'POST', body: fd })).json();
|
||||
if (res.success) renderGrid('Custom');
|
||||
};
|
||||
|
||||
div.querySelector('.edit-emote').onclick = async (e) => {
|
||||
e.stopPropagation();
|
||||
const newName = prompt("Nouveau nom (sans les :)", emote.name);
|
||||
if (!newName || newName === emote.name) return;
|
||||
const fd = new FormData();
|
||||
fd.append('id', emote.id);
|
||||
fd.append('name', newName);
|
||||
const res = await (await fetch('api/emotes.php?action=rename', { method: 'POST', body: fd })).json();
|
||||
if (res.success) renderGrid('Custom');
|
||||
};
|
||||
|
||||
grid.appendChild(div);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (uploadZone) uploadZone.classList.add('d-none');
|
||||
grid.innerHTML = '';
|
||||
const list = searchTerm ? ALL_EMOJIS.filter(e => e.includes(searchTerm)) : EMOJI_CATEGORIES[category];
|
||||
|
||||
(list || []).forEach(emoji => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'role-emoji-item rounded d-flex align-items-center justify-content-center p-2';
|
||||
div.style.cursor = 'pointer';
|
||||
div.style.fontSize = '24px';
|
||||
div.style.backgroundColor = 'rgba(255,255,255,0.05)';
|
||||
div.textContent = emoji;
|
||||
div.onclick = () => {
|
||||
navigator.clipboard.writeText(emoji);
|
||||
const originalBg = div.style.backgroundColor;
|
||||
div.style.backgroundColor = 'var(--blurple)';
|
||||
setTimeout(() => div.style.backgroundColor = originalBg, 200);
|
||||
};
|
||||
grid.appendChild(div);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
sidebar.innerHTML = '';
|
||||
categories.forEach((cat, idx) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `btn w-100 text-start text-white border-0 py-2 px-3 mb-1 d-flex align-items-center gap-2 ${idx === 0 ? 'active' : ''}`;
|
||||
btn.style.backgroundColor = idx === 0 ? 'rgba(255,255,255,0.1)' : 'transparent';
|
||||
btn.style.fontSize = '0.9em';
|
||||
btn.innerHTML = `<span>${categoryIcons[cat] || '❓'}</span> <span>${cat}</span>`;
|
||||
btn.onclick = () => {
|
||||
sidebar.querySelectorAll('button').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
b.style.backgroundColor = 'transparent';
|
||||
});
|
||||
btn.classList.add('active');
|
||||
btn.style.backgroundColor = 'rgba(255,255,255,0.1)';
|
||||
if (searchInput) searchInput.value = '';
|
||||
renderGrid(cat);
|
||||
};
|
||||
sidebar.appendChild(btn);
|
||||
});
|
||||
|
||||
if (uploadInput) {
|
||||
uploadInput.onchange = async () => {
|
||||
const file = uploadInput.files[0];
|
||||
if (!file) return;
|
||||
const name = prompt("Nom de l'emote:", file.name.split('.')[0]);
|
||||
if (!name) return;
|
||||
const fd = new FormData();
|
||||
fd.append('emote', file);
|
||||
fd.append('name', name);
|
||||
const res = await (await fetch('api/emotes.php?action=upload', { method: 'POST', body: fd })).json();
|
||||
if (res.success) {
|
||||
renderGrid('Custom');
|
||||
window.loadCustomEmotes();
|
||||
} else alert(res.error);
|
||||
uploadInput.value = '';
|
||||
};
|
||||
}
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.oninput = () => {
|
||||
const term = searchInput.value.trim();
|
||||
if (term) {
|
||||
sidebar.querySelectorAll('button').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
b.style.backgroundColor = 'transparent';
|
||||
});
|
||||
renderGrid(null, term);
|
||||
} else {
|
||||
const activeBtn = sidebar.querySelector('button.active');
|
||||
renderGrid(activeBtn ? activeBtn.querySelector('span:last-child').textContent : 'Custom');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
renderGrid('Custom');
|
||||
}
|
||||
|
||||
// Call when tab is shown
|
||||
const emotesTabBtn = document.getElementById('emotes-tab-btn');
|
||||
if (emotesTabBtn) {
|
||||
emotesTabBtn.addEventListener('shown.bs.tab', setupSettingsEmotes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Centralized Emoji Picker Component
|
||||
*/
|
||||
const UniversalEmojiPicker = {
|
||||
currentPicker: null,
|
||||
|
||||
show(anchor, callback, options = {}) {
|
||||
async show(anchor, callback, options = {}) {
|
||||
this.hide();
|
||||
|
||||
const picker = document.createElement('div');
|
||||
picker.className = 'emoji-picker p-0 overflow-hidden d-flex flex-column';
|
||||
picker.style.width = options.width || '450px';
|
||||
picker.style.width = options.width || '350px';
|
||||
picker.style.height = options.height || '450px';
|
||||
picker.style.backgroundColor = '#313338';
|
||||
picker.style.border = '1px solid #1e1f22';
|
||||
@ -89,77 +280,113 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
grid.style.gap = '5px';
|
||||
grid.style.backgroundColor = '#313338';
|
||||
|
||||
const renderGrid = (cat = null, term = '') => {
|
||||
const renderGrid = async (cat = null, term = '') => {
|
||||
grid.innerHTML = '';
|
||||
let list = term ? ALL_EMOJIS.filter(e => e.includes(term)) : EMOJI_CATEGORIES[cat];
|
||||
|
||||
(list || []).forEach(emoji => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = emoji;
|
||||
span.className = 'emoji-item rounded d-flex align-items-center justify-content-center';
|
||||
span.style.cursor = 'pointer';
|
||||
span.style.fontSize = '24px';
|
||||
span.style.padding = '8px';
|
||||
span.style.aspectRatio = '1/1';
|
||||
if (term) {
|
||||
const customEmotes = window.CUSTOM_EMOTES_CACHE || [];
|
||||
const filteredCustom = customEmotes.filter(e => e.name.toLowerCase().includes(term.toLowerCase()) || e.code.toLowerCase().includes(term.toLowerCase()));
|
||||
|
||||
span.onclick = (e) => {
|
||||
filteredCustom.forEach(emote => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'role-emoji-item rounded d-flex align-items-center justify-content-center p-2';
|
||||
div.style.cursor = 'pointer';
|
||||
div.innerHTML = `<img src="${emote.path}" style="width: 24px; height: 24px; object-fit: contain;">`;
|
||||
div.title = emote.code;
|
||||
div.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
callback(emote.code);
|
||||
if (!options.keepOpen) this.hide();
|
||||
};
|
||||
grid.appendChild(div);
|
||||
});
|
||||
|
||||
const filteredStandard = ALL_EMOJIS.filter(e => e.includes(term));
|
||||
filteredStandard.forEach(emoji => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'role-emoji-item rounded d-flex align-items-center justify-content-center p-2';
|
||||
div.style.cursor = 'pointer';
|
||||
div.style.fontSize = '24px';
|
||||
div.textContent = emoji;
|
||||
div.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
callback(emoji);
|
||||
if (!options.keepOpen) this.hide();
|
||||
};
|
||||
grid.appendChild(div);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (cat === 'Custom') {
|
||||
const customEmotes = (window.CUSTOM_EMOTES_CACHE && window.CUSTOM_EMOTES_CACHE.length > 0) ? window.CUSTOM_EMOTES_CACHE : await window.loadCustomEmotes();
|
||||
if (customEmotes.length === 0) {
|
||||
grid.innerHTML = '<div class="col-12 text-center text-muted p-4" style="grid-column: span 8; font-size: 0.8em;">Aucune emote personnalisée.</div>';
|
||||
return;
|
||||
}
|
||||
customEmotes.forEach(emote => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'role-emoji-item rounded d-flex align-items-center justify-content-center p-2';
|
||||
div.style.cursor = 'pointer';
|
||||
div.innerHTML = `<img src="${emote.path}" style="width: 24px; height: 24px; object-fit: contain;">`;
|
||||
div.title = emote.code;
|
||||
div.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
callback(emote.code);
|
||||
if (!options.keepOpen) this.hide();
|
||||
};
|
||||
grid.appendChild(div);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let list = EMOJI_CATEGORIES[cat];
|
||||
(list || []).forEach(emoji => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'role-emoji-item rounded d-flex align-items-center justify-content-center p-2';
|
||||
div.style.cursor = 'pointer';
|
||||
div.style.fontSize = '24px';
|
||||
div.textContent = emoji;
|
||||
div.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
callback(emoji);
|
||||
if (!options.keepOpen) this.hide();
|
||||
};
|
||||
grid.appendChild(span);
|
||||
grid.appendChild(div);
|
||||
});
|
||||
};
|
||||
|
||||
// Init Tabs
|
||||
const categoryIcons = {
|
||||
'Smileys': '😀',
|
||||
'Gestures': '👌',
|
||||
'People': '👶',
|
||||
'Animals': '🐶',
|
||||
'Nature': '🌵',
|
||||
'Food': '🍏',
|
||||
'Activities': '⚽️',
|
||||
'Travel': '🚗',
|
||||
'Objects': '⌚️',
|
||||
'Symbols': '❤️',
|
||||
'Flags': '🏁'
|
||||
};
|
||||
|
||||
Object.keys(EMOJI_CATEGORIES).forEach((cat, idx) => {
|
||||
const cats = ['Custom', ...Object.keys(EMOJI_CATEGORIES)];
|
||||
cats.forEach((cat, idx) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `btn btn-sm text-nowrap px-2 py-2 border-0 ${idx === 0 ? 'btn-primary' : 'btn-dark'}`;
|
||||
btn.style.fontSize = '1.2em';
|
||||
btn.className = `btn btn-sm text-white border-0 p-2 ${idx === 0 ? 'active' : ''}`;
|
||||
btn.style.backgroundColor = idx === 0 ? 'rgba(255,255,255,0.1)' : 'transparent';
|
||||
btn.innerHTML = categoryIcons[cat] || '❓';
|
||||
btn.title = cat;
|
||||
btn.textContent = categoryIcons[cat] || '❓';
|
||||
btn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
searchInput.value = '';
|
||||
btn.onclick = async () => {
|
||||
tabs.querySelectorAll('button').forEach(b => {
|
||||
b.classList.remove('btn-primary');
|
||||
b.classList.add('btn-dark');
|
||||
b.classList.remove('active');
|
||||
b.style.backgroundColor = 'transparent';
|
||||
});
|
||||
btn.classList.add('btn-primary');
|
||||
btn.classList.remove('btn-dark');
|
||||
renderGrid(cat);
|
||||
};
|
||||
tabs.appendChild(btn);
|
||||
});
|
||||
btn.classList.add('btn-primary');
|
||||
btn.classList.remove('btn-dark');
|
||||
renderGrid(cat);
|
||||
btn.classList.add('active');
|
||||
btn.style.backgroundColor = 'rgba(255,255,255,0.1)';
|
||||
await renderGrid(cat);
|
||||
};
|
||||
tabs.appendChild(btn);
|
||||
});
|
||||
|
||||
searchInput.oninput = () => {
|
||||
searchInput.oninput = async () => {
|
||||
const term = searchInput.value.trim();
|
||||
if (term) {
|
||||
tabs.querySelectorAll('button').forEach(b => b.classList.replace('btn-primary', 'btn-dark'));
|
||||
renderGrid(null, term);
|
||||
tabs.querySelectorAll('button').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
b.style.backgroundColor = 'transparent';
|
||||
});
|
||||
await renderGrid(null, term);
|
||||
} else {
|
||||
const activeCat = tabs.querySelector('button.btn-primary')?.textContent || Object.keys(EMOJI_CATEGORIES)[0];
|
||||
renderGrid(activeCat);
|
||||
const activeBtn = tabs.querySelector('button.active');
|
||||
const activeCat = activeBtn ? activeBtn.title : 'Custom';
|
||||
await renderGrid(activeCat);
|
||||
}
|
||||
};
|
||||
|
||||
@ -180,7 +407,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
picker.style.top = `${top}px`;
|
||||
picker.style.left = `${left}px`;
|
||||
|
||||
renderGrid(Object.keys(EMOJI_CATEGORIES)[0]);
|
||||
await renderGrid('Custom');
|
||||
|
||||
// Handle outside click
|
||||
const outsideClick = (e) => {
|
||||
@ -203,45 +430,83 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Replace old showEmojiPicker and role grid logic
|
||||
window.showEmojiPicker = (anchor, callback) => UniversalEmojiPicker.show(anchor, callback, { width: '350px', height: '400px' });
|
||||
|
||||
// Update role editor emoji triggers
|
||||
function setupRoleEmojiTriggers() {
|
||||
const triggers = [
|
||||
{ btn: 'role-emoji-select-btn', target: 'edit-role-icon', preview: 'selected-role-emoji-preview' },
|
||||
{ btn: 'add-autorole-emoji-btn', target: 'add-autorole-icon', preview: 'add-autorole-emoji-preview' },
|
||||
{ btn: 'edit-autorole-emoji-btn', target: 'edit-autorole-icon', preview: 'edit-autorole-emoji-preview' }
|
||||
];
|
||||
|
||||
triggers.forEach(t => {
|
||||
const btn = document.getElementById(t.btn);
|
||||
if (btn) {
|
||||
btn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
UniversalEmojiPicker.show(btn, (emoji) => {
|
||||
const input = document.getElementById(t.target);
|
||||
const preview = document.getElementById(t.preview);
|
||||
if (input) input.value = emoji;
|
||||
if (preview) preview.textContent = emoji;
|
||||
}, { width: '350px', height: '400px' });
|
||||
};
|
||||
window.renderEmojiToElement = (code, el) => {
|
||||
if (!el) return;
|
||||
if (!code) {
|
||||
el.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
if (typeof code === "string" && code.startsWith(':') && code.endsWith(':')) {
|
||||
const ce = window.CUSTOM_EMOTES_CACHE.find(e => e.code === code);
|
||||
if (ce) {
|
||||
el.innerHTML = `<img src="${ce.path}" style="width: 32px; height: 32px; object-fit: contain;">`;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
el.textContent = code;
|
||||
};
|
||||
|
||||
// Unified Emoji Picker & Modal Logic
|
||||
document.addEventListener("click", (e) => {
|
||||
// Emoji Picker Triggers
|
||||
const triggers = {
|
||||
"role-emoji-select-btn": { target: "edit-role-icon", preview: "selected-role-emoji-preview" },
|
||||
"add-autorole-emoji-btn": { target: "add-autorole-icon", preview: "add-autorole-emoji-preview" },
|
||||
"edit-autorole-emoji-btn": { target: "edit-autorole-icon", preview: "edit-autorole-emoji-preview" }
|
||||
};
|
||||
|
||||
const btn = e.target.closest("button[id]");
|
||||
if (btn && triggers[btn.id]) {
|
||||
e.preventDefault();
|
||||
const config = triggers[btn.id];
|
||||
UniversalEmojiPicker.show(btn, (emoji) => {
|
||||
const input = document.getElementById(config.target);
|
||||
const preview = document.getElementById(config.preview);
|
||||
if (input) input.value = emoji;
|
||||
window.renderEmojiToElement(emoji, preview);
|
||||
}, { width: "350px", height: "400px" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Chat Emoji Picker
|
||||
const chatEmojiBtn = document.getElementById('chat-emoji-btn');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
if (chatEmojiBtn && chatInput) {
|
||||
chatEmojiBtn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
UniversalEmojiPicker.show(chatEmojiBtn, (emoji) => {
|
||||
const chatEmojiBtn = e.target.closest("#chat-emoji-btn");
|
||||
if (chatEmojiBtn) {
|
||||
e.preventDefault();
|
||||
UniversalEmojiPicker.show(chatEmojiBtn, (emoji) => {
|
||||
const chatInput = document.getElementById("chat-input");
|
||||
if (chatInput) {
|
||||
chatInput.value += emoji;
|
||||
chatInput.focus();
|
||||
}, { keepOpen: true, width: '350px', height: '400px' });
|
||||
};
|
||||
}
|
||||
}, { keepOpen: true, width: "350px", height: "400px" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Call setup
|
||||
setupRoleEmojiTriggers();
|
||||
// Autorole Edit modal filling
|
||||
const editAutoroleBtn = e.target.closest(".edit-autorole-btn");
|
||||
if (editAutoroleBtn) {
|
||||
const id = editAutoroleBtn.dataset.id;
|
||||
const icon = editAutoroleBtn.dataset.icon;
|
||||
const title = editAutoroleBtn.dataset.title;
|
||||
const roleId = editAutoroleBtn.dataset.roleId;
|
||||
|
||||
const idInput = document.getElementById("edit-autorole-id");
|
||||
const iconInput = document.getElementById("edit-autorole-icon");
|
||||
const titleInput = document.getElementById("edit-autorole-title");
|
||||
const roleIdInput = document.getElementById("edit-autorole-role-id");
|
||||
const preview = document.getElementById("edit-autorole-emoji-preview");
|
||||
|
||||
if (idInput) idInput.value = id;
|
||||
if (iconInput) iconInput.value = icon;
|
||||
if (titleInput) titleInput.value = title;
|
||||
if (roleIdInput) roleIdInput.value = roleId;
|
||||
|
||||
if (preview) window.renderEmojiToElement(icon, preview);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
window.loadCustomEmotes();
|
||||
|
||||
// Scroll to bottom
|
||||
scrollToBottom(true);
|
||||
@ -2158,6 +2423,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function parseCustomEmotes(text) {
|
||||
let parsed = escapeHTML(text);
|
||||
(window.CUSTOM_EMOTES_CACHE || []).forEach(emote => {
|
||||
const imgHtml = `<img src="${emote.path}" alt="${emote.name}" title="${emote.code}" style="width: 22px; height: 22px; vertical-align: middle; object-fit: contain;">`;
|
||||
parsed = parsed.split(emote.code).join(imgHtml);
|
||||
});
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function appendMessage(msg) {
|
||||
if (!msg || !msg.id) return;
|
||||
if (document.querySelector(`.message-item[data-id="${msg.id}"]`)) return;
|
||||
@ -2238,53 +2512,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
` : '';
|
||||
|
||||
const mentionRegex = new RegExp(`@${window.currentUsername}\\b`, 'g');
|
||||
if (msg.content.match(mentionRegex)) {
|
||||
div.classList.add('mentioned');
|
||||
}
|
||||
if (msg.is_pinned) div.classList.add('pinned');
|
||||
|
||||
const ytRegex = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
|
||||
const dmRegex = /(?:https?:\/\/)?(?:www\.)?(?:dailymotion\.com\/video\/|dai\.ly\/)([a-zA-Z0-9]+)/;
|
||||
const vimeoRegex = /(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(\d+)/;
|
||||
|
||||
const ytMatch = msg.content.match(ytRegex);
|
||||
const dmMatch = msg.content.match(dmRegex);
|
||||
const vimeoMatch = msg.content.match(vimeoRegex);
|
||||
|
||||
let videoHtml = '';
|
||||
if (ytMatch && ytMatch[1]) {
|
||||
videoHtml = `<div class="video-embed mt-2"><iframe width="100%" height="315" src="https://www.youtube.com/embed/${ytMatch[1]}?autoplay=0" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="border-radius: 8px; max-width: 560px;"></iframe></div>`;
|
||||
} else if (dmMatch && dmMatch[1]) {
|
||||
videoHtml = `<div class="video-embed mt-2"><iframe width="100%" height="315" src="https://www.dailymotion.com/embed/video/${dmMatch[1]}?autoplay=0&queue-enable=0&mute=0" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="border-radius: 8px; max-width: 560px;"></iframe></div>`;
|
||||
} else if (vimeoMatch && vimeoMatch[1]) {
|
||||
videoHtml = `<div class="video-embed mt-2"><iframe width="100%" height="315" src="https://player.vimeo.com/video/${vimeoMatch[1]}?autoplay=0" frameborder="0" allow="fullscreen; picture-in-picture" allowfullscreen style="border-radius: 8px; max-width: 560px;"></iframe></div>`;
|
||||
}
|
||||
|
||||
const authorStyle = msg.role_color ? `color: ${msg.role_color};` : '';
|
||||
|
||||
const isRoleIconUrl = msg.role_icon && (msg.role_icon.startsWith('http') || msg.role_icon.startsWith('/'));
|
||||
const roleIcon = msg.role_icon ? (isRoleIconUrl ? `<img src="${msg.role_icon}" class="role-icon ms-1" style="width: 12px; height: 12px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 12px; vertical-align: middle;">${msg.role_icon}</span>`) : '';
|
||||
const mentionHtml = `<span class="mention">@${window.currentUsername}</span>`;
|
||||
const contentWithMentions = parseCustomEmotes(msg.content).replace(mentionRegex, mentionHtml);
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="message-avatar" style="${avatarStyle}"></div>
|
||||
<div class="message-content">
|
||||
<div class="message-header">
|
||||
<span class="message-author" style="${authorStyle}">${escapeHTML(msg.username)}</span>
|
||||
${roleIcon}
|
||||
<span class="message-time">${msg.time}</span>
|
||||
<span class="message-username" style="color: ${msg.user_role_color || 'inherit'};">${escapeHTML(msg.username)}</span>
|
||||
<span class="message-timestamp">${msg.timestamp || 'Just now'}</span>
|
||||
${pinnedBadge}
|
||||
</div>
|
||||
<div class="message-text">
|
||||
${escapeHTML(msg.content).replace(/\n/g, '<br>').replace(mentionRegex, `<span class="mention">@${window.currentUsername}</span>`)}
|
||||
${attachmentHtml}
|
||||
${videoHtml}
|
||||
${embedHtml}
|
||||
</div>
|
||||
<div class="message-reactions mt-1" data-message-id="${msg.id}">
|
||||
<span class="add-reaction-btn" title="Add Reaction">+</span>
|
||||
</div>
|
||||
<div class="message-text">${contentWithMentions.replace(/\n/g, '<br>')}</div>
|
||||
${attachmentHtml}
|
||||
${embedHtml}
|
||||
<div class="message-reactions mt-1" data-message-id="${msg.id}"></div>
|
||||
</div>
|
||||
${actionsHtml}
|
||||
<div class="message-reaction-picker-anchor"></div>
|
||||
`;
|
||||
messagesList.appendChild(div);
|
||||
scrollToBottom(isMe);
|
||||
@ -2352,45 +2597,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Edit Autorole Modal Population
|
||||
document.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.edit-autorole-btn');
|
||||
if (!btn) return;
|
||||
|
||||
const id = btn.dataset.id;
|
||||
const icon = btn.dataset.icon;
|
||||
const title = btn.dataset.title;
|
||||
const roleId = btn.dataset.roleId;
|
||||
|
||||
document.getElementById('edit-autorole-id').value = id;
|
||||
document.getElementById('edit-autorole-icon').value = icon;
|
||||
document.getElementById('edit-autorole-title').value = title;
|
||||
document.getElementById('edit-autorole-role-id').value = roleId;
|
||||
|
||||
// Update preview
|
||||
const preview = document.getElementById('edit-autorole-emoji-preview');
|
||||
if (preview) preview.textContent = icon;
|
||||
});
|
||||
|
||||
// Universal Emoji Picker Trigger
|
||||
document.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.open-emoji-picker');
|
||||
if (!btn) return;
|
||||
|
||||
const targetId = btn.dataset.target;
|
||||
const targetInput = document.querySelector(targetId);
|
||||
|
||||
if (typeof showEmojiPicker === 'function') {
|
||||
showEmojiPicker(btn, (emoji) => {
|
||||
if (targetInput) {
|
||||
targetInput.value = emoji;
|
||||
// Special case for role settings preview
|
||||
if (targetId === '#edit-role-icon') {
|
||||
const preview = document.getElementById('selected-role-emoji-preview');
|
||||
if (preview) preview.textContent = emoji;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
BIN
assets/pasted-20260216-132213-80b79cbe.png
Normal file
BIN
assets/pasted-20260216-132213-80b79cbe.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
BIN
assets/pasted-20260216-162915-c3590120.png
Normal file
BIN
assets/pasted-20260216-162915-c3590120.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
7
db/migrations/001_create_custom_emotes.sql
Normal file
7
db/migrations/001_create_custom_emotes.sql
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS custom_emotes (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(50) NOT NULL,
|
||||
path VARCHAR(255) NOT NULL,
|
||||
code VARCHAR(60) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
69
index.php
69
index.php
@ -7,15 +7,45 @@ function renderRoleIcon($icon, $size = '12px') {
|
||||
if (empty($icon)) return '';
|
||||
$isUrl = (strpos($icon, 'http') === 0 || strpos($icon, '/') === 0);
|
||||
$isFa = (strpos($icon, 'fa-') === 0);
|
||||
$isCustomEmote = (strpos($icon, ':') === 0 && substr($icon, -1) === ':');
|
||||
|
||||
if ($isUrl) {
|
||||
return '<img src="' . htmlspecialchars($icon) . '" class="role-icon ms-1" style="width: '.$size.'; height: '.$size.'; vertical-align: middle; object-fit: contain;">';
|
||||
} elseif ($isFa) {
|
||||
return '<i class="fa-solid ' . htmlspecialchars($icon) . ' ms-1" style="font-size: '.$size.'; vertical-align: middle;"></i>';
|
||||
} elseif ($isCustomEmote) {
|
||||
// Fetch emote path
|
||||
static $ce_icons_cache;
|
||||
if ($ce_icons_cache === null) {
|
||||
try { $ce_icons_cache = db()->query("SELECT code, path FROM custom_emotes")->fetchAll(PDO::FETCH_KEY_PAIR); } catch (Exception $e) { $ce_icons_cache = []; }
|
||||
}
|
||||
if (isset($ce_icons_cache[$icon])) {
|
||||
return '<img src="' . htmlspecialchars($ce_icons_cache[$icon]) . '" class="role-icon ms-1" style="width: '.$size.'; height: '.$size.'; vertical-align: middle; object-fit: contain;">';
|
||||
}
|
||||
return '<span class="ms-1" style="font-size: '.$size.'; vertical-align: middle;">' . htmlspecialchars($icon) . '</span>';
|
||||
} else {
|
||||
return '<span class="ms-1" style="font-size: '.$size.'; vertical-align: middle;">' . htmlspecialchars($icon) . '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to parse emotes in content
|
||||
function parse_emotes($content) {
|
||||
static $custom_emotes_cache;
|
||||
if ($custom_emotes_cache === null) {
|
||||
try {
|
||||
$custom_emotes_cache = db()->query("SELECT name, path, code FROM custom_emotes")->fetchAll();
|
||||
} catch (Exception $e) {
|
||||
$custom_emotes_cache = [];
|
||||
}
|
||||
}
|
||||
|
||||
$result = htmlspecialchars($content);
|
||||
foreach ($custom_emotes_cache as $ce) {
|
||||
$emote_html = '<img src="' . htmlspecialchars($ce['path']) . '" alt="' . htmlspecialchars($ce['name']) . '" title="' . htmlspecialchars($ce['code']) . '" style="width: 22px; height: 22px; vertical-align: middle; object-fit: contain;">';
|
||||
$result = str_replace($ce['code'], $emote_html, $result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
requireLogin();
|
||||
|
||||
$user = getCurrentUser();
|
||||
@ -672,7 +702,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
data-role-id="<?php echo $ar['role_id']; ?>"
|
||||
data-id="<?php echo $ar['id']; ?>"
|
||||
style="<?php echo $has_role ? 'background-color: var(--blurple); border: none;' : 'background-color: #2b2d31; border: none; color: white;'; ?>">
|
||||
<span style="font-size: 1.5em;"><?php echo htmlspecialchars($ar['icon']); ?></span>
|
||||
<span style="font-size: 1.5em;"><?php echo parse_emotes($ar['icon']); ?></span>
|
||||
<div class="text-start">
|
||||
<div class="fw-bold"><?php echo htmlspecialchars($ar['title']); ?></div>
|
||||
<div class="small opacity-75"><?php echo htmlspecialchars($ar['role_name']); ?></div>
|
||||
@ -801,6 +831,17 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<?php
|
||||
$msg_content = htmlspecialchars($m['content']);
|
||||
$msg_content = preg_replace($mention_pattern, '<span class="mention">@' . htmlspecialchars($user['username']) . '</span>', $msg_content);
|
||||
|
||||
// Custom Emotes parsing
|
||||
static $custom_emotes_cache;
|
||||
if ($custom_emotes_cache === null) {
|
||||
$custom_emotes_cache = db()->query("SELECT name, path, code FROM custom_emotes")->fetchAll();
|
||||
}
|
||||
foreach ($custom_emotes_cache as $ce) {
|
||||
$emote_html = '<img src="' . htmlspecialchars($ce['path']) . '" alt="' . htmlspecialchars($ce['name']) . '" title="' . htmlspecialchars($ce['code']) . '" style="width: 22px; height: 22px; vertical-align: middle; object-fit: contain;">';
|
||||
$msg_content = str_replace($ce['code'], $emote_html, $msg_content);
|
||||
}
|
||||
|
||||
echo nl2br($msg_content);
|
||||
?>
|
||||
<?php if ($m['attachment_url']): ?>
|
||||
@ -851,7 +892,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
$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>
|
||||
<?php echo parse_emotes($r['emoji']); ?> <span class="count"><?php echo $r['count']; ?></span>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
<span class="add-reaction-btn" title="Add Reaction">+</span>
|
||||
@ -1024,6 +1065,9 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<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="emotes-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-emotes" type="button">Emotes</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>
|
||||
@ -1138,6 +1182,27 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="settings-emotes">
|
||||
<div class="row g-0" style="height: 450px;">
|
||||
<div class="col-3 border-end border-secondary overflow-auto custom-scrollbar" id="settings-emotes-sidebar" style="background-color: #2b2d31;">
|
||||
<!-- Categories will be loaded here -->
|
||||
</div>
|
||||
<div class="col-9 d-flex flex-column" style="background-color: #313338;">
|
||||
<div class="p-2 border-bottom border-secondary d-flex gap-2">
|
||||
<input type="text" id="settings-emotes-search" class="form-control form-control-sm bg-dark border-secondary text-white" placeholder="Rechercher une emote...">
|
||||
<div id="custom-emote-upload-zone" class="d-none">
|
||||
<button class="btn btn-primary btn-sm" onclick="document.getElementById('emote-upload-input').click()">
|
||||
<i class="fas fa-plus"></i> Ajouter
|
||||
</button>
|
||||
<input type="file" id="emote-upload-input" class="d-none" accept="image/png,image/jpeg">
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings-emotes-grid" class="flex-grow-1 overflow-auto p-3 custom-scrollbar" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); gap: 10px;">
|
||||
<!-- Emojis will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
25
requests.log
25
requests.log
@ -209,3 +209,28 @@
|
||||
2026-02-16 13:06:41 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||
2026-02-16 13:06:43 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||
2026-02-16 13:06:45 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 13:11:31 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-16 13:13:37 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 13:18:51 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-16 13:19:22 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 13:25:42 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-16 13:38:52 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 13:44:04 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-16 14:10:45 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 14:10:52 - GET /index.php - POST: []
|
||||
2026-02-16 14:10:57 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 14:11:06 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 14:13:29 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-16 16:20:04 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 16:20:11 - GET /index.php - POST: []
|
||||
2026-02-16 16:20:31 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 16:20:47 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 16:23:59 - GET / - POST: []
|
||||
2026-02-16 16:24:07 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-16 16:24:48 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 16:24:51 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 16:25:36 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 16:32:41 - GET / - POST: []
|
||||
2026-02-16 16:32:49 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-16 16:32:51 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
2026-02-16 16:33:28 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user