Nouvelles fonctionnalités : - Système de modération Twitch complet (bans, timeouts, avertissements) - Filtre de liens intelligent pour Twitch avec whitelist/blacklist - Notifications d'événements Twitch (follows, subs, raids, etc.) - Système Freeloot Discord avec flux RSS dédié - Salons automatiques Discord (création/suppression dynamique) - Authentification utilisateur pour l'interface web (login/register) - Gestion des utilisateurs et permissions - Interface de modération Twitch dans la webapp - Interface de gestion des événements Twitch - Configuration des paramètres utilisateur - Migration BDD pour les mots bannis Améliorations de l'interface : - Refonte complète des templates (configurations, commandes, humeurs, etc.) - Nouvelle page de settings utilisateur - Page de gestion des utilisateurs (admin) - Page d'erreur 403 personnalisée - Amélioration de la navigation et du design global - Intégration de nouvelles sections dans le menu principal Modifications techniques : - Ajout de nouveaux modèles en base de données - Extension des helpers database - Mise à jour des dépendances (requirements.txt) - Amélioration de la gestion des annonces Twitch - Refactorisation du code pour meilleure maintenabilité Co-authored-by: Cursor <cursoragent@cursor.com>
343 lines
20 KiB
HTML
343 lines
20 KiB
HTML
{% extends "template.html" %}
|
|
|
|
{% block content %}
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-4">Notifications YouTube</h1>
|
|
|
|
{% if msg %}
|
|
<div id="alert-msg" class="mb-4 p-4 rounded-lg {{ 'bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-300' if msg_type == 'error' else 'bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 text-green-700 dark:text-green-300' }}">
|
|
{{ msg }}
|
|
</div>
|
|
<script>
|
|
setTimeout(function() {
|
|
var el = document.getElementById('alert-msg');
|
|
if (el) el.style.display = 'none';
|
|
}, 5000);
|
|
</script>
|
|
{% endif %}
|
|
|
|
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
|
<p class="text-gray-700 dark:text-gray-300">
|
|
Liste des chaînes YouTube surveillées pour les notifications de nouvelles vidéos.
|
|
Le bot vérifie toutes les 5 minutes les nouvelles vidéos des chaînes en dessous.
|
|
Quand une nouvelle vidéo est détectée, le bot enverra une notification sur Discord.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{% if not notification %}
|
|
<div class="mb-8">
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Notifications configurées</h2>
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full">
|
|
<thead class="bg-gray-50 dark:bg-gray-700">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Chaîne YouTube</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Canal Discord</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Type</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Message</th>
|
|
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
{% for notification in notifications %}
|
|
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<code class="px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded text-red-600 dark:text-red-400 font-mono text-sm">{{ notification.channel_id }}</code>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-gray-700 dark:text-gray-300">{{ notification.notify_channel_name }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="px-2 py-1 text-xs font-medium rounded-full
|
|
{% if notification.video_type == 'all' %}bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300
|
|
{% elif notification.video_type == 'video' %}bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300
|
|
{% else %}bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300{% endif %}">
|
|
{% if notification.video_type == 'all' %}Toutes
|
|
{% elif notification.video_type == 'video' %}Vidéos
|
|
{% else %}Shorts{% endif %}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 text-gray-600 dark:text-gray-400 max-w-xs truncate">{{ notification.message }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-center">
|
|
<div class="flex items-center justify-center gap-2">
|
|
<a href="{{ url_for('toggleYouTube', id = notification.id) }}"
|
|
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors"
|
|
title="{{ 'Désactiver' if notification.enable else 'Activer' }}">
|
|
{{ '✅' if notification.enable else '❌' }}
|
|
</a>
|
|
<a href="{{ url_for('openEditYouTube', id = notification.id) }}"
|
|
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors text-blue-600 dark:text-blue-400"
|
|
title="Modifier">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
|
</a>
|
|
<a href="{{ url_for('delYouTube', id = notification.id) }}"
|
|
onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette notification ?')"
|
|
class="p-2 rounded-lg hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors text-red-600 dark:text-red-400"
|
|
title="Supprimer">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="5" class="px-6 py-8 text-center text-gray-500 dark:text-gray-400">
|
|
Aucune notification configurée. Ajoutez-en une ci-dessous.
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-6">
|
|
{{ 'Modifier la notification' if notification else 'Ajouter une notification YouTube' }}
|
|
</h2>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
<div>
|
|
<form id="youtube-form" action="{{ url_for('submitEditYouTube', id = notification.id) if notification else url_for('addYouTube') }}" method="POST" class="space-y-6">
|
|
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4 space-y-4">
|
|
<h3 class="font-medium text-gray-800 dark:text-gray-200">Configuration de base</h3>
|
|
|
|
<div>
|
|
<label for="channel_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Lien ou ID de la chaîne YouTube</label>
|
|
<input name="channel_id" id="channel_id" type="text" maxlength="256" required
|
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all"
|
|
placeholder="https://www.youtube.com/@513v3 ou UC..."
|
|
value="{{notification.channel_id if notification}}"/>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="notify_channel" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Canal de notification Discord</label>
|
|
<select name="notify_channel" id="notify_channel"
|
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all">
|
|
{% for channel in channels %}
|
|
<option value="{{channel.id}}" {% if notification and notification.notify_channel == channel.id %}selected{% endif %}>{{channel.name}}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="video_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Type de vidéo à notifier</label>
|
|
<select name="video_type" id="video_type"
|
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all">
|
|
<option value="all" {% if notification and notification.video_type == 'all' %}selected{% endif %}>Toutes (vidéos + shorts)</option>
|
|
<option value="video" {% if notification and notification.video_type == 'video' %}selected{% endif %}>Vidéos uniquement</option>
|
|
<option value="short" {% if notification and notification.video_type == 'short' %}selected{% endif %}>Shorts uniquement</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="message" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Message (optionnel)</label>
|
|
<textarea name="message" id="message" rows="2"
|
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all resize-y"
|
|
placeholder="Message envoyé avant l'embed">{{notification.message if notification}}</textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4 space-y-4">
|
|
<h3 class="font-medium text-gray-800 dark:text-gray-200">Personnalisation de l'embed Discord</h3>
|
|
|
|
<div>
|
|
<label for="embed_title" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Titre de l'embed</label>
|
|
<input name="embed_title" id="embed_title" type="text" maxlength="256"
|
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all"
|
|
placeholder="{video_title}"
|
|
value="{{notification.embed_title if notification}}"/>
|
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Variables: {video_title}, {channel_name}, {video_url}, {video_id}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="embed_description" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Description de l'embed</label>
|
|
<textarea name="embed_description" id="embed_description" rows="2"
|
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all resize-y"
|
|
placeholder="Description optionnelle">{{notification.embed_description if notification}}</textarea>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label for="embed_color" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Couleur</label>
|
|
<div class="flex items-center gap-2">
|
|
<input name="embed_color" id="embed_color" type="color"
|
|
class="w-12 h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer"
|
|
value="#{{notification.embed_color if notification else 'FF0000'}}"/>
|
|
<input type="text" id="embed_color_text" maxlength="6"
|
|
class="flex-1 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm"
|
|
value="{{notification.embed_color if notification else 'FF0000'}}" placeholder="FF0000"/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="embed_author_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Nom de l'auteur</label>
|
|
<input name="embed_author_name" id="embed_author_name" type="text" maxlength="256"
|
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all"
|
|
placeholder="{channel_name}"
|
|
value="{{notification.embed_author_name if notification}}"/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="embed_author_icon" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Icône de l'auteur (URL)</label>
|
|
<input name="embed_author_icon" id="embed_author_icon" type="text" maxlength="512"
|
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all"
|
|
placeholder="https://www.youtube.com/img/desktop/yt_1200.png"
|
|
value="{{notification.embed_author_icon if notification}}"/>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="embed_footer" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Pied de page</label>
|
|
<input name="embed_footer" id="embed_footer" type="text" maxlength="2048"
|
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all"
|
|
placeholder="Texte optionnel en bas"
|
|
value="{{notification.embed_footer if notification}}"/>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-4">
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
<input type="checkbox" name="embed_thumbnail" id="embed_thumbnail"
|
|
{% if not notification or notification.embed_thumbnail %}checked{% endif %}
|
|
class="w-5 h-5 rounded border-gray-300 dark:border-gray-600 text-red-600 focus:ring-red-500 dark:bg-gray-700">
|
|
<span class="text-sm text-gray-700 dark:text-gray-300">Miniature</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
<input type="checkbox" name="embed_image" id="embed_image"
|
|
{% if not notification or notification.embed_image %}checked{% endif %}
|
|
class="w-5 h-5 rounded border-gray-300 dark:border-gray-600 text-red-600 focus:ring-red-500 dark:bg-gray-700">
|
|
<span class="text-sm text-gray-700 dark:text-gray-300">Image principale</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-4">
|
|
<button type="submit"
|
|
class="px-6 py-2.5 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
|
{{ 'Enregistrer' if notification else 'Ajouter la notification' }}
|
|
</button>
|
|
{% if notification %}
|
|
<a href="{{ url_for('youtube') }}"
|
|
class="px-6 py-2.5 bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 text-gray-700 dark:text-gray-200 font-medium rounded-lg transition-colors">
|
|
Annuler
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 class="font-medium text-gray-800 dark:text-gray-200 mb-4">Prévisualisation de l'embed Discord</h3>
|
|
<div id="embed-preview" class="bg-[#2f3136] rounded p-4 font-sans text-[#dcddde] max-w-xl border-l-4" style="border-left-color: #FF0000;">
|
|
<div id="embed-author" class="flex items-center mb-2 text-sm">
|
|
<img id="embed-author-icon" src="https://www.youtube.com/img/desktop/yt_1200.png" class="w-5 h-5 rounded-full mr-2" onerror="this.style.display='none'"/>
|
|
<span id="embed-author-name" class="font-semibold">Nom de la chaîne</span>
|
|
</div>
|
|
<a id="embed-title" href="#" class="text-[#00aff4] no-underline text-base font-semibold block mb-2">Titre de la vidéo</a>
|
|
<div id="embed-description" class="text-sm leading-relaxed mb-2 text-[#dcddde]"></div>
|
|
<div id="embed-thumbnail-container" class="my-2">
|
|
<img id="embed-thumbnail" src="" class="max-w-[80px] max-h-[80px] rounded float-right ml-4 hidden"/>
|
|
</div>
|
|
<div id="embed-image-container" class="mt-4">
|
|
<img id="embed-image" src="https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" class="max-w-full rounded hidden"/>
|
|
</div>
|
|
<div id="embed-footer" class="mt-2 text-xs text-[#72767d]"></div>
|
|
</div>
|
|
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">Cette prévisualisation est approximative.</p>
|
|
|
|
<div class="mt-6 bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4">
|
|
<h4 class="font-medium text-gray-800 dark:text-gray-200 mb-2">Variables disponibles</h4>
|
|
<ul class="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
|
<li><code class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded">{channel_name}</code> — Nom de la chaîne</li>
|
|
<li><code class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded">{video_title}</code> — Titre de la vidéo</li>
|
|
<li><code class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded">{video_url}</code> — Lien vers la vidéo</li>
|
|
<li><code class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded">{video_id}</code> — ID de la vidéo</li>
|
|
<li><code class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded">{thumbnail}</code> — URL de la miniature</li>
|
|
<li><code class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded">{published_at}</code> — Date de publication</li>
|
|
<li><code class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded">{is_short}</code> — True si c'est un short</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function formatText(text, vars) {
|
|
if (!text) return '';
|
|
return text.replace(/\{(\w+)\}/g, function(match, key) {
|
|
return vars[key] || match;
|
|
});
|
|
}
|
|
|
|
function updatePreview() {
|
|
const embedTitle = document.getElementById('embed_title').value || '{video_title}';
|
|
const embedDescription = document.getElementById('embed_description').value || '';
|
|
const embedColor = document.getElementById('embed_color_text').value || 'FF0000';
|
|
const embedAuthorName = document.getElementById('embed_author_name').value || '{channel_name}';
|
|
const embedAuthorIcon = document.getElementById('embed_author_icon').value || 'https://www.youtube.com/img/desktop/yt_1200.png';
|
|
const embedFooter = document.getElementById('embed_footer').value || '';
|
|
const embedThumbnail = document.getElementById('embed_thumbnail').checked;
|
|
const embedImage = document.getElementById('embed_image').checked;
|
|
|
|
const vars = {
|
|
video_title: 'Nouvelle vidéo de test',
|
|
channel_name: 'Ma Chaîne YouTube',
|
|
video_url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
|
video_id: 'dQw4w9WgXcQ',
|
|
thumbnail: 'https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
|
published_at: '2026-01-25T12:00:00Z',
|
|
is_short: false
|
|
};
|
|
|
|
document.getElementById('embed-title').textContent = formatText(embedTitle, vars);
|
|
document.getElementById('embed-title').href = vars.video_url;
|
|
document.getElementById('embed-description').textContent = formatText(embedDescription, vars);
|
|
document.getElementById('embed-author-name').textContent = formatText(embedAuthorName, vars);
|
|
document.getElementById('embed-author-icon').src = embedAuthorIcon;
|
|
document.getElementById('embed-footer').textContent = formatText(embedFooter, vars);
|
|
|
|
document.getElementById('embed-preview').style.borderLeftColor = '#' + embedColor;
|
|
|
|
if (embedThumbnail) {
|
|
document.getElementById('embed-thumbnail').src = vars.thumbnail;
|
|
document.getElementById('embed-thumbnail').style.display = 'block';
|
|
} else {
|
|
document.getElementById('embed-thumbnail').style.display = 'none';
|
|
}
|
|
|
|
if (embedImage) {
|
|
document.getElementById('embed-image').src = vars.thumbnail;
|
|
document.getElementById('embed-image').style.display = 'block';
|
|
} else {
|
|
document.getElementById('embed-image').style.display = 'none';
|
|
}
|
|
}
|
|
|
|
document.getElementById('embed_color').addEventListener('input', function(e) {
|
|
document.getElementById('embed_color_text').value = e.target.value.substring(1).toUpperCase();
|
|
updatePreview();
|
|
});
|
|
|
|
document.getElementById('embed_color_text').addEventListener('input', function(e) {
|
|
const val = e.target.value.replace(/[^0-9A-Fa-f]/g, '').substring(0, 6);
|
|
e.target.value = val;
|
|
if (val.length === 6) {
|
|
document.getElementById('embed_color').value = '#' + val;
|
|
updatePreview();
|
|
}
|
|
});
|
|
|
|
const formFields = ['embed_title', 'embed_description', 'embed_author_name', 'embed_author_icon', 'embed_footer', 'embed_thumbnail', 'embed_image'];
|
|
formFields.forEach(field => {
|
|
const el = document.getElementById(field);
|
|
if (el) {
|
|
el.addEventListener('input', updatePreview);
|
|
el.addEventListener('change', updatePreview);
|
|
}
|
|
});
|
|
|
|
updatePreview();
|
|
</script>
|
|
{% endblock %}
|