|
|
<!DOCTYPE html> |
|
|
<html lang="fr"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>SBI BOOK - Réseau Social</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
|
|
|
.online-status::after { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
bottom: 0; |
|
|
right: 0; |
|
|
width: 12px; |
|
|
height: 12px; |
|
|
background-color: #4ade80; |
|
|
border-radius: 50%; |
|
|
border: 2px solid white; |
|
|
} |
|
|
|
|
|
.file-input-label { |
|
|
cursor: pointer; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.file-input-label:hover { |
|
|
transform: scale(1.05); |
|
|
} |
|
|
|
|
|
.comment-reply { |
|
|
margin-left: 40px; |
|
|
border-left: 2px solid #e5e7eb; |
|
|
padding-left: 10px; |
|
|
} |
|
|
|
|
|
.audio-recorder { |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); } |
|
|
70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } |
|
|
100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } |
|
|
} |
|
|
|
|
|
.modal { |
|
|
transition: opacity 0.3s ease, transform 0.3s ease; |
|
|
} |
|
|
|
|
|
.modal.hidden { |
|
|
opacity: 0; |
|
|
transform: scale(0.9); |
|
|
pointer-events: none; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-100 min-h-screen flex flex-col"> |
|
|
|
|
|
<div id="authModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> |
|
|
<div class="bg-white rounded-lg p-6 w-full max-w-md"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-2xl font-bold text-blue-600">SBI BOOK</h2> |
|
|
<button id="closeAuthModal" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div id="authTabs" class="flex border-b mb-4"> |
|
|
<button id="loginTab" class="px-4 py-2 font-medium text-blue-600 border-b-2 border-blue-600">Connexion</button> |
|
|
<button id="registerTab" class="px-4 py-2 font-medium text-gray-500">Inscription</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="loginForm"> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="loginUsername">Nom d'utilisateur</label> |
|
|
<input type="text" id="loginUsername" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
<div class="mb-4 relative"> |
|
|
<label class="block text-gray-700 mb-2" for="loginPassword">Mot de passe</label> |
|
|
<input type="password" id="loginPassword" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
<button id="toggleLoginPassword" class="absolute right-3 top-9 text-gray-500"> |
|
|
<i class="fas fa-eye"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div class="mb-4 flex justify-between items-center"> |
|
|
<button id="forgotPassword" class="text-blue-600 text-sm">Mot de passe oublié ?</button> |
|
|
</div> |
|
|
<button id="loginBtn" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">Se connecter</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="registerForm" class="hidden"> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="fullName">Nom complet*</label> |
|
|
<input type="text" id="fullName" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required> |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="birthDate">Date de naissance*</label> |
|
|
<input type="date" id="birthDate" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required> |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="location">Lieu de résidence*</label> |
|
|
<input type="text" id="location" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required> |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="username">Nom d'utilisateur</label> |
|
|
<input type="text" id="username" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
<div class="mb-4 relative"> |
|
|
<label class="block text-gray-700 mb-2" for="password">Mot de passe*</label> |
|
|
<input type="password" id="password" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required> |
|
|
<button id="toggleRegisterPassword" class="absolute right-3 top-9 text-gray-500"> |
|
|
<i class="fas fa-eye"></i> |
|
|
</button> |
|
|
</div> |
|
|
<button id="registerBtn" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">S'inscrire</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="forgotPasswordForm" class="hidden"> |
|
|
<h3 class="text-lg font-medium mb-4">Réinitialiser le mot de passe</h3> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="forgotUsername">Nom d'utilisateur</label> |
|
|
<input type="text" id="forgotUsername" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="forgotEmail">Email associé</label> |
|
|
<input type="email" id="forgotEmail" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
<button id="resetPasswordBtn" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition mb-2">Réinitialiser</button> |
|
|
<button id="backToLogin" class="w-full bg-gray-200 text-gray-800 py-2 rounded-lg hover:bg-gray-300 transition">Retour</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="app" class="hidden flex-1 flex flex-col"> |
|
|
|
|
|
<header class="bg-white shadow-sm sticky top-0 z-10"> |
|
|
<div class="container mx-auto px-4 py-3 flex justify-between items-center"> |
|
|
<div class="flex items-center"> |
|
|
<h1 class="text-2xl font-bold text-blue-600">SBI BOOK</h1> |
|
|
</div> |
|
|
<div class="flex items-center space-x-4"> |
|
|
<div id="userMenu" class="relative"> |
|
|
<button id="userMenuButton" class="flex items-center space-x-2 focus:outline-none"> |
|
|
<div class="w-10 h-10 rounded-full bg-gray-300 overflow-hidden relative"> |
|
|
<img id="userAvatar" src="" alt="Profile" class="w-full h-full object-cover"> |
|
|
<div class="online-status"></div> |
|
|
</div> |
|
|
<span id="userNameDisplay" class="font-medium"></span> |
|
|
</button> |
|
|
<div id="userDropdown" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-20"> |
|
|
<a href="#" id="profileLink" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">Profil</a> |
|
|
<a href="#" id="settingsLink" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">Paramètres</a> |
|
|
<a href="#" id="logoutLink" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">Déconnexion</a> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<div class="container mx-auto px-4 py-4"> |
|
|
<div class="bg-white rounded-lg shadow p-4 mb-4"> |
|
|
<div class="flex items-start space-x-3"> |
|
|
<div class="w-12 h-12 rounded-full bg-gray-300 overflow-hidden relative"> |
|
|
<img id="currentUserAvatar" src="" alt="Profile" class="w-full h-full object-cover"> |
|
|
<div class="online-status"></div> |
|
|
</div> |
|
|
<div class="flex-1"> |
|
|
<textarea id="postContent" placeholder="À quoi pensez-vous ?" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none" rows="2"></textarea> |
|
|
<div id="postPreview" class="mt-2 hidden"></div> |
|
|
<div class="flex justify-between items-center mt-3"> |
|
|
<div class="flex space-x-2"> |
|
|
<label for="postImage" class="file-input-label text-green-600 cursor-pointer"> |
|
|
<i class="fas fa-image"></i> Photo |
|
|
</label> |
|
|
<input type="file" id="postImage" accept="image/*" class="hidden"> |
|
|
|
|
|
<label for="postVideo" class="file-input-label text-blue-600 cursor-pointer"> |
|
|
<i class="fas fa-video"></i> Vidéo |
|
|
</label> |
|
|
<input type="file" id="postVideo" accept="video/*" class="hidden"> |
|
|
|
|
|
<label for="postAudio" class="file-input-label text-purple-600 cursor-pointer"> |
|
|
<i class="fas fa-music"></i> Audio |
|
|
</label> |
|
|
<input type="file" id="postAudio" accept="audio/*" class="hidden"> |
|
|
|
|
|
<button id="startRecording" class="text-red-600"> |
|
|
<i class="fas fa-microphone"></i> Vocal |
|
|
</button> |
|
|
</div> |
|
|
<button id="postSubmit" class="bg-blue-600 text-white px-4 py-1 rounded-lg hover:bg-blue-700 transition">Publier</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="container mx-auto px-4 flex flex-col md:flex-row gap-4 flex-1"> |
|
|
|
|
|
<div class="w-full md:w-1/4 lg:w-1/5"> |
|
|
<div class="bg-white rounded-lg shadow p-4 mb-4"> |
|
|
<h3 class="font-bold text-lg mb-3">Menu</h3> |
|
|
<ul class="space-y-2"> |
|
|
<li><a href="#" class="flex items-center space-x-2 text-blue-600"><i class="fas fa-home"></i> <span>Fil d'actualité</span></a></li> |
|
|
<li><a href="#" class="flex items-center space-x-2"><i class="fas fa-user-friends"></i> <span>Amis</span></a></li> |
|
|
<li><a href="#" id="groupsLink" class="flex items-center space-x-2"><i class="fas fa-users"></i> <span>Groupes</span></a></li> |
|
|
<li><a href="#" class="flex items-center space-x-2"><i class="fas fa-envelope"></i> <span>Messages</span></a></li> |
|
|
</ul> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-4"> |
|
|
<h3 class="font-bold text-lg mb-3">Groupes</h3> |
|
|
<div id="groupsList" class="space-y-2"> |
|
|
|
|
|
</div> |
|
|
<button id="createGroupBtn" class="mt-3 w-full bg-gray-200 text-gray-800 py-1 rounded-lg hover:bg-gray-300 transition">+ Créer un groupe</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="w-full md:w-2/4 lg:w-3/5"> |
|
|
<div id="postsContainer" class="space-y-4"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="w-full md:w-1/4 lg:w-1/5"> |
|
|
<div class="bg-white rounded-lg shadow p-4 mb-4"> |
|
|
<h3 class="font-bold text-lg mb-3">Contacts</h3> |
|
|
<div id="contactsList" class="space-y-3"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<footer class="bg-white shadow-md py-3 mt-5"> |
|
|
<div class="container mx-auto px-4"> |
|
|
<div class="flex justify-around"> |
|
|
<a href="#" class="flex flex-col items-center text-gray-700"> |
|
|
<i class="fas fa-comments text-xl"></i> |
|
|
<span class="text-xs mt-1">Discussions</span> |
|
|
</a> |
|
|
<a href="#" class="flex flex-col items-center text-blue-600"> |
|
|
<i class="fas fa-newspaper text-xl"></i> |
|
|
<span class="text-xs mt-1">Actus</span> |
|
|
</a> |
|
|
<a href="#" class="flex flex-col items-center text-gray-700"> |
|
|
<i class="fas fa-users text-xl"></i> |
|
|
<span class="text-xs mt-1">Groupes</span> |
|
|
</a> |
|
|
</div> |
|
|
</div> |
|
|
</footer> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="createGroupModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-lg p-6 w-full max-w-md"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-bold">Créer un groupe</h2> |
|
|
<button id="closeGroupModal" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="groupName">Nom du groupe*</label> |
|
|
<input type="text" id="groupName" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="groupDescription">Description</label> |
|
|
<textarea id="groupDescription" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" rows="3"></textarea> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2" for="groupImage">Image de couverture</label> |
|
|
<input type="file" id="groupImage" accept="image/*" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
|
|
|
<button id="createGroupSubmit" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">Créer</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-lg p-6 w-full max-w-md"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-bold">Paramètres</h2> |
|
|
<button id="closeSettingsModal" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4"> |
|
|
<h3 class="font-medium mb-2">Profil</h3> |
|
|
<div class="flex items-center space-x-4 mb-3"> |
|
|
<div class="w-16 h-16 rounded-full bg-gray-300 overflow-hidden relative"> |
|
|
<img id="settingsAvatar" src="" alt="Profile" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
<div> |
|
|
<label for="settingsAvatarInput" class="text-blue-600 cursor-pointer">Changer la photo</label> |
|
|
<input type="file" id="settingsAvatarInput" accept="image/*" class="hidden"> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-3"> |
|
|
<label class="block text-gray-700 mb-1" for="settingsFullName">Nom complet</label> |
|
|
<input type="text" id="settingsFullName" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
|
|
|
<div class="mb-3"> |
|
|
<label class="block text-gray-700 mb-1" for="settingsUsername">Nom d'utilisateur</label> |
|
|
<input type="text" id="settingsUsername" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
|
|
|
<div class="mb-3"> |
|
|
<label class="block text-gray-700 mb-1" for="settingsLocation">Lieu de résidence</label> |
|
|
<input type="text" id="settingsLocation" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4"> |
|
|
<h3 class="font-medium mb-2">Sécurité</h3> |
|
|
<button id="changePasswordBtn" class="w-full text-left py-2 px-3 bg-gray-100 rounded-lg mb-2 hover:bg-gray-200">Changer le mot de passe</button> |
|
|
<button id="blockedUsersBtn" class="w-full text-left py-2 px-3 bg-gray-100 rounded-lg hover:bg-gray-200">Utilisateurs bloqués</button> |
|
|
</div> |
|
|
|
|
|
<button id="saveSettings" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">Enregistrer</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="changePasswordModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-lg p-6 w-full max-w-md"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-bold">Changer le mot de passe</h2> |
|
|
<button id="closeChangePasswordModal" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4 relative"> |
|
|
<label class="block text-gray-700 mb-2" for="currentPassword">Mot de passe actuel</label> |
|
|
<input type="password" id="currentPassword" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
<button id="toggleCurrentPassword" class="absolute right-3 top-9 text-gray-500"> |
|
|
<i class="fas fa-eye"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4 relative"> |
|
|
<label class="block text-gray-700 mb-2" for="newPassword">Nouveau mot de passe</label> |
|
|
<input type="password" id="newPassword" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
<button id="toggleNewPassword" class="absolute right-3 top-9 text-gray-500"> |
|
|
<i class="fas fa-eye"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4 relative"> |
|
|
<label class="block text-gray-700 mb-2" for="confirmNewPassword">Confirmer le nouveau mot de passe</label> |
|
|
<input type="password" id="confirmNewPassword" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
<button id="toggleConfirmNewPassword" class="absolute right-3 top-9 text-gray-500"> |
|
|
<i class="fas fa-eye"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<button id="submitPasswordChange" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">Changer</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="blockedUsersModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-lg p-6 w-full max-w-md"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-bold">Utilisateurs bloqués</h2> |
|
|
<button id="closeBlockedUsersModal" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div id="blockedUsersList" class="space-y-3 max-h-64 overflow-y-auto"> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="mt-4 text-center text-gray-500" id="noBlockedUsers"> |
|
|
Aucun utilisateur bloqué |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="audioRecorder" class="fixed bottom-4 right-4 bg-white p-4 rounded-lg shadow-lg hidden"> |
|
|
<div class="flex items-center space-x-4"> |
|
|
<button id="stopRecording" class="text-red-600"> |
|
|
<i class="fas fa-stop-circle text-2xl"></i> |
|
|
</button> |
|
|
<div class="flex-1"> |
|
|
<div class="flex items-center"> |
|
|
<div id="recordingTimer" class="text-gray-700 mr-3">00:00</div> |
|
|
<div id="recordingVisualizer" class="flex-1 h-4 bg-gray-200 rounded-full overflow-hidden"> |
|
|
<div class="h-full bg-blue-500 animate-pulse" style="width: 30%"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
const state = { |
|
|
currentUser: null, |
|
|
users: JSON.parse(localStorage.getItem('users')) || [], |
|
|
posts: JSON.parse(localStorage.getItem('posts')) || [], |
|
|
groups: JSON.parse(localStorage.getItem('groups')) || [], |
|
|
blockedUsers: JSON.parse(localStorage.getItem('blockedUsers')) || {}, |
|
|
audioRecorder: null, |
|
|
recordingStartTime: null, |
|
|
recordingTimer: null |
|
|
}; |
|
|
|
|
|
|
|
|
const authModal = document.getElementById('authModal'); |
|
|
const app = document.getElementById('app'); |
|
|
const loginForm = document.getElementById('loginForm'); |
|
|
const registerForm = document.getElementById('registerForm'); |
|
|
const forgotPasswordForm = document.getElementById('forgotPasswordForm'); |
|
|
const loginTab = document.getElementById('loginTab'); |
|
|
const registerTab = document.getElementById('registerTab'); |
|
|
const loginBtn = document.getElementById('loginBtn'); |
|
|
const registerBtn = document.getElementById('registerBtn'); |
|
|
const forgotPassword = document.getElementById('forgotPassword'); |
|
|
const resetPasswordBtn = document.getElementById('resetPasswordBtn'); |
|
|
const backToLogin = document.getElementById('backToLogin'); |
|
|
const closeAuthModal = document.getElementById('closeAuthModal'); |
|
|
const toggleLoginPassword = document.getElementById('toggleLoginPassword'); |
|
|
const toggleRegisterPassword = document.getElementById('toggleRegisterPassword'); |
|
|
const loginUsername = document.getElementById('loginUsername'); |
|
|
const loginPassword = document.getElementById('loginPassword'); |
|
|
const fullName = document.getElementById('fullName'); |
|
|
const birthDate = document.getElementById('birthDate'); |
|
|
const location = document.getElementById('location'); |
|
|
const username = document.getElementById('username'); |
|
|
const password = document.getElementById('password'); |
|
|
const forgotUsername = document.getElementById('forgotUsername'); |
|
|
const forgotEmail = document.getElementById('forgotEmail'); |
|
|
const userMenuButton = document.getElementById('userMenuButton'); |
|
|
const userDropdown = document.getElementById('userDropdown'); |
|
|
const logoutLink = document.getElementById('logoutLink'); |
|
|
const profileLink = document.getElementById('profileLink'); |
|
|
const settingsLink = document.getElementById('settingsLink'); |
|
|
const userNameDisplay = document.getElementById('userNameDisplay'); |
|
|
const userAvatar = document.getElementById('userAvatar'); |
|
|
const currentUserAvatar = document.getElementById('currentUserAvatar'); |
|
|
const postContent = document.getElementById('postContent'); |
|
|
const postSubmit = document.getElementById('postSubmit'); |
|
|
const postImage = document.getElementById('postImage'); |
|
|
const postVideo = document.getElementById('postVideo'); |
|
|
const postAudio = document.getElementById('postAudio'); |
|
|
const postPreview = document.getElementById('postPreview'); |
|
|
const postsContainer = document.getElementById('postsContainer'); |
|
|
const startRecording = document.getElementById('startRecording'); |
|
|
const audioRecorder = document.getElementById('audioRecorder'); |
|
|
const stopRecording = document.getElementById('stopRecording'); |
|
|
const recordingTimer = document.getElementById('recordingTimer'); |
|
|
const createGroupBtn = document.getElementById('createGroupBtn'); |
|
|
const createGroupModal = document.getElementById('createGroupModal'); |
|
|
const closeGroupModal = document.getElementById('closeGroupModal'); |
|
|
const groupName = document.getElementById('groupName'); |
|
|
const groupDescription = document.getElementById('groupDescription'); |
|
|
const groupImage = document.getElementById('groupImage'); |
|
|
const createGroupSubmit = document.getElementById('createGroupSubmit'); |
|
|
const groupsList = document.getElementById('groupsList'); |
|
|
const groupsLink = document.getElementById('groupsLink'); |
|
|
const contactsList = document.getElementById('contactsList'); |
|
|
const settingsModal = document.getElementById('settingsModal'); |
|
|
const closeSettingsModal = document.getElementById('closeSettingsModal'); |
|
|
const settingsAvatar = document.getElementById('settingsAvatar'); |
|
|
const settingsAvatarInput = document.getElementById('settingsAvatarInput'); |
|
|
const settingsFullName = document.getElementById('settingsFullName'); |
|
|
const settingsUsername = document.getElementById('settingsUsername'); |
|
|
const settingsLocation = document.getElementById('settingsLocation'); |
|
|
const saveSettings = document.getElementById('saveSettings'); |
|
|
const changePasswordBtn = document.getElementById('changePasswordBtn'); |
|
|
const changePasswordModal = document.getElementById('changePasswordModal'); |
|
|
const closeChangePasswordModal = document.getElementById('closeChangePasswordModal'); |
|
|
const currentPassword = document.getElementById('currentPassword'); |
|
|
const newPassword = document.getElementById('newPassword'); |
|
|
const confirmNewPassword = document.getElementById('confirmNewPassword'); |
|
|
const submitPasswordChange = document.getElementById('submitPasswordChange'); |
|
|
const toggleCurrentPassword = document.getElementById('toggleCurrentPassword'); |
|
|
const toggleNewPassword = document.getElementById('toggleNewPassword'); |
|
|
const toggleConfirmNewPassword = document.getElementById('toggleConfirmNewPassword'); |
|
|
const blockedUsersBtn = document.getElementById('blockedUsersBtn'); |
|
|
const blockedUsersModal = document.getElementById('blockedUsersModal'); |
|
|
const closeBlockedUsersModal = document.getElementById('closeBlockedUsersModal'); |
|
|
const blockedUsersList = document.getElementById('blockedUsersList'); |
|
|
const noBlockedUsers = document.getElementById('noBlockedUsers'); |
|
|
|
|
|
|
|
|
function init() { |
|
|
|
|
|
const loggedInUser = localStorage.getItem('currentUser'); |
|
|
if (loggedInUser) { |
|
|
state.currentUser = JSON.parse(loggedInUser); |
|
|
renderApp(); |
|
|
} else { |
|
|
authModal.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
setupEventListeners(); |
|
|
} |
|
|
|
|
|
|
|
|
function setupEventListeners() { |
|
|
|
|
|
loginTab.addEventListener('click', () => { |
|
|
loginTab.classList.add('border-blue-600', 'text-blue-600'); |
|
|
loginTab.classList.remove('text-gray-500'); |
|
|
registerTab.classList.add('text-gray-500'); |
|
|
registerTab.classList.remove('border-blue-600', 'text-blue-600'); |
|
|
loginForm.classList.remove('hidden'); |
|
|
registerForm.classList.add('hidden'); |
|
|
forgotPasswordForm.classList.add('hidden'); |
|
|
}); |
|
|
|
|
|
registerTab.addEventListener('click', () => { |
|
|
registerTab.classList.add('border-blue-600', 'text-blue-600'); |
|
|
registerTab.classList.remove('text-gray-500'); |
|
|
loginTab.classList.add('text-gray-500'); |
|
|
loginTab.classList.remove('border-blue-600', 'text-blue-600'); |
|
|
registerForm.classList.remove('hidden'); |
|
|
loginForm.classList.add('hidden'); |
|
|
forgotPasswordForm.classList.add('hidden'); |
|
|
}); |
|
|
|
|
|
loginBtn.addEventListener('click', handleLogin); |
|
|
registerBtn.addEventListener('click', handleRegister); |
|
|
forgotPassword.addEventListener('click', () => { |
|
|
forgotPasswordForm.classList.remove('hidden'); |
|
|
loginForm.classList.add('hidden'); |
|
|
registerForm.classList.add('hidden'); |
|
|
}); |
|
|
|
|
|
resetPasswordBtn.addEventListener('click', handleResetPassword); |
|
|
backToLogin.addEventListener('click', () => { |
|
|
forgotPasswordForm.classList.add('hidden'); |
|
|
loginForm.classList.remove('hidden'); |
|
|
}); |
|
|
|
|
|
closeAuthModal.addEventListener('click', () => { |
|
|
authModal.classList.add('hidden'); |
|
|
}); |
|
|
|
|
|
toggleLoginPassword.addEventListener('click', () => { |
|
|
togglePasswordVisibility(loginPassword, toggleLoginPassword); |
|
|
}); |
|
|
|
|
|
toggleRegisterPassword.addEventListener('click', () => { |
|
|
togglePasswordVisibility(password, toggleRegisterPassword); |
|
|
}); |
|
|
|
|
|
|
|
|
userMenuButton.addEventListener('click', () => { |
|
|
userDropdown.classList.toggle('hidden'); |
|
|
}); |
|
|
|
|
|
logoutLink.addEventListener('click', handleLogout); |
|
|
settingsLink.addEventListener('click', () => { |
|
|
userDropdown.classList.add('hidden'); |
|
|
openSettingsModal(); |
|
|
}); |
|
|
|
|
|
profileLink.addEventListener('click', () => { |
|
|
userDropdown.classList.add('hidden'); |
|
|
|
|
|
alert('Page de profil'); |
|
|
}); |
|
|
|
|
|
|
|
|
postSubmit.addEventListener('click', createPost); |
|
|
postImage.addEventListener('change', handleFileSelect); |
|
|
postVideo.addEventListener('change', handleFileSelect); |
|
|
postAudio.addEventListener('change', handleFileSelect); |
|
|
startRecording.addEventListener('click', startAudioRecording); |
|
|
stopRecording.addEventListener('click', stopAudioRecording); |
|
|
|
|
|
|
|
|
createGroupBtn.addEventListener('click', () => { |
|
|
createGroupModal.classList.remove('hidden'); |
|
|
}); |
|
|
|
|
|
closeGroupModal.addEventListener('click', () => { |
|
|
createGroupModal.classList.add('hidden'); |
|
|
}); |
|
|
|
|
|
createGroupSubmit.addEventListener('click', createGroup); |
|
|
groupsLink.addEventListener('click', () => { |
|
|
|
|
|
alert('Page des groupes'); |
|
|
}); |
|
|
|
|
|
|
|
|
closeSettingsModal.addEventListener('click', () => { |
|
|
settingsModal.classList.add('hidden'); |
|
|
}); |
|
|
|
|
|
saveSettings.addEventListener('click', saveUserSettings); |
|
|
settingsAvatarInput.addEventListener('change', handleAvatarChange); |
|
|
|
|
|
changePasswordBtn.addEventListener('click', () => { |
|
|
settingsModal.classList.add('hidden'); |
|
|
changePasswordModal.classList.remove('hidden'); |
|
|
}); |
|
|
|
|
|
closeChangePasswordModal.addEventListener('click', () => { |
|
|
changePasswordModal.classList.add('hidden'); |
|
|
settingsModal.classList.remove('hidden'); |
|
|
}); |
|
|
|
|
|
submitPasswordChange.addEventListener('click', changePassword); |
|
|
|
|
|
toggleCurrentPassword.addEventListener('click', () => { |
|
|
togglePasswordVisibility(currentPassword, toggleCurrentPassword); |
|
|
}); |
|
|
|
|
|
toggleNewPassword.addEventListener('click', () => { |
|
|
togglePasswordVisibility(newPassword, toggleNewPassword); |
|
|
}); |
|
|
|
|
|
toggleConfirmNewPassword.addEventListener('click', () => { |
|
|
togglePasswordVisibility(confirmNewPassword, toggleConfirmNewPassword); |
|
|
}); |
|
|
|
|
|
|
|
|
blockedUsersBtn.addEventListener('click', () => { |
|
|
settingsModal.classList.add('hidden'); |
|
|
blockedUsersModal.classList.remove('hidden'); |
|
|
renderBlockedUsers(); |
|
|
}); |
|
|
|
|
|
closeBlockedUsersModal.addEventListener('click', () => { |
|
|
blockedUsersModal.classList.add('hidden'); |
|
|
settingsModal.classList.remove('hidden'); |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => { |
|
|
if (!userMenuButton.contains(e.target) && !userDropdown.contains(e.target)) { |
|
|
userDropdown.classList.add('hidden'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function togglePasswordVisibility(passwordField, toggleButton) { |
|
|
if (passwordField.type === 'password') { |
|
|
passwordField.type = 'text'; |
|
|
toggleButton.innerHTML = '<i class="fas fa-eye-slash"></i>'; |
|
|
} else { |
|
|
passwordField.type = 'password'; |
|
|
toggleButton.innerHTML = '<i class="fas fa-eye"></i>'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function handleLogin() { |
|
|
const username = loginUsername.value.trim(); |
|
|
const password = loginPassword.value.trim(); |
|
|
|
|
|
if (!username || !password) { |
|
|
alert('Veuillez remplir tous les champs'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const user = state.users.find(u => |
|
|
(u.username === username || u.fullName === username) && u.password === password |
|
|
); |
|
|
|
|
|
if (user) { |
|
|
state.currentUser = user; |
|
|
localStorage.setItem('currentUser', JSON.stringify(user)); |
|
|
authModal.classList.add('hidden'); |
|
|
renderApp(); |
|
|
} else { |
|
|
alert('Nom d\'utilisateur ou mot de passe incorrect'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function handleRegister() { |
|
|
const fullNameValue = fullName.value.trim(); |
|
|
const birthDateValue = birthDate.value; |
|
|
const locationValue = location.value.trim(); |
|
|
const usernameValue = username.value.trim(); |
|
|
const passwordValue = password.value.trim(); |
|
|
|
|
|
if (!fullNameValue || !birthDateValue || !locationValue || !passwordValue) { |
|
|
alert('Veuillez remplir tous les champs obligatoires'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (usernameValue && state.users.some(u => u.username === usernameValue)) { |
|
|
alert('Ce nom d\'utilisateur est déjà pris'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const newUser = { |
|
|
id: Date.now().toString(), |
|
|
fullName: fullNameValue, |
|
|
birthDate: birthDateValue, |
|
|
location: locationValue, |
|
|
username: usernameValue || fullNameValue.toLowerCase().replace(/\s+/g, ''), |
|
|
password: passwordValue, |
|
|
avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(fullNameValue)}&background=random`, |
|
|
joinedDate: new Date().toISOString() |
|
|
}; |
|
|
|
|
|
state.users.push(newUser); |
|
|
localStorage.setItem('users', JSON.stringify(state.users)); |
|
|
|
|
|
state.currentUser = newUser; |
|
|
localStorage.setItem('currentUser', JSON.stringify(newUser)); |
|
|
|
|
|
authModal.classList.add('hidden'); |
|
|
renderApp(); |
|
|
|
|
|
alert('Inscription réussie ! Bienvenue sur SBI BOOK'); |
|
|
} |
|
|
|
|
|
|
|
|
function handleResetPassword() { |
|
|
const usernameValue = forgotUsername.value.trim(); |
|
|
const emailValue = forgotEmail.value.trim(); |
|
|
|
|
|
if (!usernameValue || !emailValue) { |
|
|
alert('Veuillez remplir tous les champs'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const user = state.users.find(u => |
|
|
(u.username === usernameValue || u.fullName === usernameValue) && |
|
|
u.email === emailValue |
|
|
); |
|
|
|
|
|
if (user) { |
|
|
|
|
|
alert('Un lien de réinitialisation a été envoyé à votre email'); |
|
|
forgotPasswordForm.classList.add('hidden'); |
|
|
loginForm.classList.remove('hidden'); |
|
|
} else { |
|
|
alert('Aucun compte trouvé avec ces informations'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function handleLogout() { |
|
|
state.currentUser = null; |
|
|
localStorage.removeItem('currentUser'); |
|
|
app.classList.add('hidden'); |
|
|
authModal.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
loginUsername.value = ''; |
|
|
loginPassword.value = ''; |
|
|
fullName.value = ''; |
|
|
birthDate.value = ''; |
|
|
location.value = ''; |
|
|
username.value = ''; |
|
|
password.value = ''; |
|
|
|
|
|
|
|
|
loginTab.click(); |
|
|
} |
|
|
|
|
|
|
|
|
function renderApp() { |
|
|
app.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
userNameDisplay.textContent = state.currentUser.fullName; |
|
|
userAvatar.src = state.currentUser.avatar; |
|
|
currentUserAvatar.src = state.currentUser.avatar; |
|
|
|
|
|
|
|
|
renderPosts(); |
|
|
|
|
|
|
|
|
renderGroups(); |
|
|
|
|
|
|
|
|
renderContacts(); |
|
|
} |
|
|
|
|
|
|
|
|
function renderPosts() { |
|
|
postsContainer.innerHTML = ''; |
|
|
|
|
|
|
|
|
const visiblePosts = state.posts.filter(post => { |
|
|
return !state.blockedUsers[state.currentUser.id]?.includes(post.userId); |
|
|
}); |
|
|
|
|
|
if (visiblePosts.length === 0) { |
|
|
postsContainer.innerHTML = ` |
|
|
<div class="bg-white rounded-lg shadow p-6 text-center"> |
|
|
<p class="text-gray-500">Aucune publication à afficher. Soyez le premier à publier !</p> |
|
|
</div> |
|
|
`; |
|
|
return; |
|
|
} |
|
|
|
|
|
visiblePosts.forEach(post => { |
|
|
const postUser = state.users.find(u => u.id === post.userId) || { |
|
|
fullName: 'Utilisateur inconnu', |
|
|
avatar: 'https://ui-avatars.com/api/?name=Unknown&background=random' |
|
|
}; |
|
|
|
|
|
const postElement = document.createElement('div'); |
|
|
postElement.className = 'bg-white rounded-lg shadow p-4'; |
|
|
postElement.dataset.postId = post.id; |
|
|
|
|
|
let mediaElement = ''; |
|
|
if (post.media) { |
|
|
if (post.media.type.startsWith('image')) { |
|
|
mediaElement = ` |
|
|
<div class="mt-3"> |
|
|
<img src="${post.media.url}" alt="Post image" class="w-full rounded-lg"> |
|
|
</div> |
|
|
`; |
|
|
} else if (post.media.type.startsWith('video')) { |
|
|
mediaElement = ` |
|
|
<div class="mt-3"> |
|
|
<video controls class="w-full rounded-lg"> |
|
|
<source src="${post.media.url}" type="${post.media.type}"> |
|
|
Votre navigateur ne supporte pas les vidéos. |
|
|
</video> |
|
|
</div> |
|
|
`; |
|
|
} else if (post.media.type.startsWith('audio')) { |
|
|
mediaElement = ` |
|
|
<div class="mt-3"> |
|
|
<audio controls class="w-full"> |
|
|
<source src="${post.media.url}" type="${post.media.type}"> |
|
|
Votre navigateur ne supporte pas les audios. |
|
|
</audio> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
let groupInfo = ''; |
|
|
if (post.groupId) { |
|
|
const group = state.groups.find(g => g.id === post.groupId); |
|
|
if (group) { |
|
|
groupInfo = ` |
|
|
<div class="text-sm text-gray-500 mb-2"> |
|
|
<i class="fas fa-users mr-1"></i> Publié dans ${group.name} |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
const isLiked = post.likes?.includes(state.currentUser.id) || false; |
|
|
|
|
|
postElement.innerHTML = ` |
|
|
<div class="flex items-start space-x-3"> |
|
|
<div class="w-10 h-10 rounded-full bg-gray-300 overflow-hidden relative"> |
|
|
<img src="${postUser.avatar}" alt="Profile" class="w-full h-full object-cover"> |
|
|
<div class="online-status"></div> |
|
|
</div> |
|
|
<div class="flex-1"> |
|
|
<div class="flex justify-between items-start"> |
|
|
<div> |
|
|
<h3 class="font-medium">${postUser.fullName}</h3> |
|
|
<p class="text-xs text-gray-500">${formatDate(post.createdAt)}</p> |
|
|
</div> |
|
|
<div class="relative"> |
|
|
<button class="post-options text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-ellipsis-h"></i> |
|
|
</button> |
|
|
<div class="post-options-menu hidden absolute right-0 mt-1 w-40 bg-white rounded-md shadow-lg py-1 z-10"> |
|
|
${post.userId === state.currentUser.id ? ` |
|
|
<a href="#" class="block px-4 py-2 text-gray-800 hover:bg-gray-100 delete-post">Supprimer</a> |
|
|
` : ` |
|
|
<a href="#" class="block px-4 py-2 text-gray-800 hover:bg-gray-100 block-user">Bloquer cet utilisateur</a> |
|
|
`} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
${groupInfo} |
|
|
<p class="mt-2">${post.content}</p> |
|
|
${mediaElement} |
|
|
<div class="mt-3 flex justify-between text-gray-500 border-t pt-3"> |
|
|
<button class="like-btn flex items-center space-x-1 ${isLiked ? 'text-blue-600' : ''}"> |
|
|
<i class="fas fa-thumbs-up"></i> |
|
|
<span>${post.likes?.length || 0}</span> |
|
|
</button> |
|
|
<button class="comment-btn flex items-center space-x-1"> |
|
|
<i class="fas fa-comment"></i> |
|
|
<span>${post.comments?.length || 0}</span> |
|
|
</button> |
|
|
<button class="share-btn flex items-center space-x-1"> |
|
|
<i class="fas fa-share"></i> |
|
|
<span>Partager</span> |
|
|
</button> |
|
|
${post.media ? ` |
|
|
<a href="${post.media.url}" download class="flex items-center space-x-1 text-green-600"> |
|
|
<i class="fas fa-download"></i> |
|
|
<span>Télécharger</span> |
|
|
</a> |
|
|
` : ''} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="post-comments mt-3 hidden"> |
|
|
<!-- Comments will be loaded here --> |
|
|
</div> |
|
|
<div class="mt-3 flex items-center"> |
|
|
<div class="w-8 h-8 rounded-full bg-gray-300 overflow-hidden mr-2"> |
|
|
<img src="${state.currentUser.avatar}" alt="Profile" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
<input type="text" placeholder="Écrire un commentaire..." class="comment-input flex-1 px-3 py-2 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
postsContainer.appendChild(postElement); |
|
|
|
|
|
|
|
|
const likeBtn = postElement.querySelector('.like-btn'); |
|
|
const commentBtn = postElement.querySelector('.comment-btn'); |
|
|
const shareBtn = postElement.querySelector('.share-btn'); |
|
|
const commentInput = postElement.querySelector('.comment-input'); |
|
|
const postOptionsBtn = postElement.querySelector('.post-options'); |
|
|
const postOptionsMenu = postElement.querySelector('.post-options-menu'); |
|
|
const deletePostBtn = postElement.querySelector('.delete-post'); |
|
|
const blockUserBtn = postElement.querySelector('.block-user'); |
|
|
|
|
|
likeBtn.addEventListener('click', () => toggleLike(post.id)); |
|
|
commentBtn.addEventListener('click', () => toggleComments(postElement, post.id)); |
|
|
shareBtn.addEventListener('click', () => sharePost(post.id)); |
|
|
|
|
|
commentInput.addEventListener('keypress', (e) => { |
|
|
if (e.key === 'Enter' && commentInput.value.trim()) { |
|
|
addComment(post.id, commentInput.value.trim()); |
|
|
commentInput.value = ''; |
|
|
} |
|
|
}); |
|
|
|
|
|
postOptionsBtn.addEventListener('click', () => { |
|
|
postOptionsMenu.classList.toggle('hidden'); |
|
|
}); |
|
|
|
|
|
if (deletePostBtn) { |
|
|
deletePostBtn.addEventListener('click', () => { |
|
|
if (confirm('Supprimer cette publication ?')) { |
|
|
deletePost(post.id); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
if (blockUserBtn) { |
|
|
blockUserBtn.addEventListener('click', () => { |
|
|
if (confirm(`Bloquer ${postUser.fullName} ? Vous ne verrez plus ses publications.`)) { |
|
|
blockUser(post.userId); |
|
|
} |
|
|
}); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function renderGroups() { |
|
|
groupsList.innerHTML = ''; |
|
|
|
|
|
if (state.groups.length === 0) { |
|
|
groupsList.innerHTML = ` |
|
|
<div class="text-gray-500 text-sm">Aucun groupe disponible</div> |
|
|
`; |
|
|
return; |
|
|
} |
|
|
|
|
|
state.groups.forEach(group => { |
|
|
const groupElement = document.createElement('div'); |
|
|
groupElement.className = 'flex items-center space-x-2 p-2 hover:bg-gray-100 rounded-lg cursor-pointer'; |
|
|
groupElement.dataset.groupId = group.id; |
|
|
|
|
|
groupElement.innerHTML = ` |
|
|
<div class="w-10 h-10 rounded-full bg-gray-300 overflow-hidden"> |
|
|
<img src="${group.image || 'https://ui-avatars.com/api/?name=' + encodeURIComponent(group.name) + '&background=random'}" alt="${group.name}" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
<div> |
|
|
<h4 class="font-medium">${group.name}</h4> |
|
|
<p class="text-xs text-gray-500">${group.members.length} membres</p> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
groupElement.addEventListener('click', () => { |
|
|
filterPostsByGroup(group.id); |
|
|
}); |
|
|
|
|
|
groupsList.appendChild(groupElement); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function renderContacts() { |
|
|
contactsList.innerHTML = ''; |
|
|
|
|
|
|
|
|
const contacts = state.users.filter(user => |
|
|
user.id !== state.currentUser.id && |
|
|
!state.blockedUsers[state.currentUser.id]?.includes(user.id) |
|
|
); |
|
|
|
|
|
if (contacts.length === 0) { |
|
|
contactsList.innerHTML = ` |
|
|
<div class="text-gray-500 text-sm">Aucun contact disponible</div> |
|
|
`; |
|
|
return; |
|
|
} |
|
|
|
|
|
contacts.forEach(user => { |
|
|
const contactElement = document.createElement('div'); |
|
|
contactElement.className = 'flex items-center space-x-2 p-2 hover:bg-gray-100 rounded-lg cursor-pointer'; |
|
|
contactElement.dataset.userId = user.id; |
|
|
|
|
|
contactElement.innerHTML = ` |
|
|
<div class="w-10 h-10 rounded-full bg-gray-300 overflow-hidden relative"> |
|
|
<img src="${user.avatar}" alt="${user.fullName}" class="w-full h-full object-cover"> |
|
|
<div class="online-status"></div> |
|
|
</div> |
|
|
<div> |
|
|
<h4 class="font-medium">${user.fullName}</h4> |
|
|
<p class="text-xs text-gray-500">${user.location}</p> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
contactElement.addEventListener('click', () => { |
|
|
|
|
|
alert(`Profil de ${user.fullName}`); |
|
|
}); |
|
|
|
|
|
contactsList.appendChild(contactElement); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function renderComments(postElement, postId) { |
|
|
const post = state.posts.find(p => p.id === postId); |
|
|
if (!post || !post.comments) return; |
|
|
|
|
|
const commentsContainer = postElement.querySelector('.post-comments'); |
|
|
commentsContainer.innerHTML = ''; |
|
|
|
|
|
post.comments.forEach(comment => { |
|
|
const commentUser = state.users.find(u => u.id === comment.userId) || { |
|
|
fullName: 'Utilisateur inconnu', |
|
|
avatar: 'https://ui-avatars.com/api/?name=Unknown&background=random' |
|
|
}; |
|
|
|
|
|
const isLiked = comment.likes?.includes(state.currentUser.id) || false; |
|
|
const isDisliked = comment.dislikes?.includes(state.currentUser.id) || false; |
|
|
|
|
|
const commentElement = document.createElement('div'); |
|
|
commentElement.className = 'mb-3'; |
|
|
commentElement.dataset.commentId = comment.id; |
|
|
|
|
|
commentElement.innerHTML = ` |
|
|
<div class="flex items-start space-x-2"> |
|
|
<div class="w-8 h-8 rounded-full bg-gray-300 overflow-hidden"> |
|
|
<img src="${commentUser.avatar}" alt="Profile" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
<div class="flex-1"> |
|
|
<div class="bg-gray-100 rounded-lg p-2"> |
|
|
<div class="flex justify-between items-start"> |
|
|
<div> |
|
|
<h4 class="font-medium text-sm">${commentUser.fullName}</h4> |
|
|
<p class="text-xs text-gray-500">${formatDate(comment.createdAt)}</p> |
|
|
</div> |
|
|
${comment.userId === state.currentUser.id ? ` |
|
|
<div class="flex space-x-1"> |
|
|
<button class="edit-comment text-gray-500 hover:text-gray-700 text-xs"> |
|
|
<i class="fas fa-edit"></i> |
|
|
</button> |
|
|
<button class="delete-comment text-gray-500 hover:text-gray-700 text-xs"> |
|
|
<i class="fas fa-trash"></i> |
|
|
</button> |
|
|
</div> |
|
|
` : ''} |
|
|
</div> |
|
|
<p class="mt-1">${comment.content}</p> |
|
|
</div> |
|
|
<div class="flex items-center space-x-3 mt-1 ml-2 text-xs"> |
|
|
<button class="like-comment flex items-center space-x-1 ${isLiked ? 'text-blue-600' : ''}"> |
|
|
<i class="fas fa-thumbs-up"></i> |
|
|
<span>${comment.likes?.length || 0}</span> |
|
|
</button> |
|
|
<button class="dislike-comment flex items-center space-x-1 ${isDisliked ? 'text-red-600' : ''}"> |
|
|
<i class="fas fa-thumbs-down"></i> |
|
|
<span>${comment.dislikes?.length || 0}</span> |
|
|
</button> |
|
|
<button class="reply-comment text-gray-500 hover:text-gray-700"> |
|
|
Répondre |
|
|
</button> |
|
|
</div> |
|
|
<div class="comment-reply-input mt-2 hidden"> |
|
|
<div class="flex items-center"> |
|
|
<div class="w-6 h-6 rounded-full bg-gray-300 overflow-hidden mr-2"> |
|
|
<img src="${state.currentUser.avatar}" alt="Profile" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
<input type="text" placeholder="Écrire une réponse..." class="flex-1 px-2 py-1 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500 text-xs"> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
commentsContainer.appendChild(commentElement); |
|
|
|
|
|
|
|
|
const likeBtn = commentElement.querySelector('.like-comment'); |
|
|
const dislikeBtn = commentElement.querySelector('.dislike-comment'); |
|
|
const replyBtn = commentElement.querySelector('.reply-comment'); |
|
|
const editBtn = commentElement.querySelector('.edit-comment'); |
|
|
const deleteBtn = commentElement.querySelector('.delete-comment'); |
|
|
|
|
|
likeBtn.addEventListener('click', () => toggleCommentLike(postId, comment.id)); |
|
|
dislikeBtn.addEventListener('click', () => toggleCommentDislike(postId, comment.id)); |
|
|
replyBtn.addEventListener('click', () => toggleReplyInput(commentElement)); |
|
|
|
|
|
if (editBtn) { |
|
|
editBtn.addEventListener('click', () => editComment(postId, comment.id)); |
|
|
} |
|
|
|
|
|
if (deleteBtn) { |
|
|
deleteBtn.addEventListener('click', () => { |
|
|
if (confirm('Supprimer ce commentaire ?')) { |
|
|
deleteComment(postId, comment.id); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
if (comment.replies && comment.replies.length > 0) { |
|
|
renderReplies(commentElement, postId, comment.id, comment.replies); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function renderReplies(commentElement, postId, commentId, replies) { |
|
|
const repliesContainer = document.createElement('div'); |
|
|
repliesContainer.className = 'comment-reply mt-2'; |
|
|
|
|
|
replies.forEach(reply => { |
|
|
const replyUser = state.users.find(u => u.id === reply.userId) || { |
|
|
fullName: 'Utilisateur inconnu', |
|
|
avatar: 'https://ui-avatars.com/api/?name=Unknown&background=random' |
|
|
}; |
|
|
|
|
|
const replyElement = document.createElement('div'); |
|
|
replyElement.className = 'mb-2'; |
|
|
replyElement.dataset.replyId = reply.id; |
|
|
|
|
|
replyElement.innerHTML = ` |
|
|
<div class="flex items-start space-x-2"> |
|
|
<div class="w-6 h-6 rounded-full bg-gray-300 overflow-hidden"> |
|
|
<img src="${replyUser.avatar}" alt="Profile" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
<div class="flex-1"> |
|
|
<div class="bg-gray-100 rounded-lg p-2"> |
|
|
<div class="flex justify-between items-start"> |
|
|
<div> |
|
|
<h4 class="font-medium text-xs">${replyUser.fullName}</h4> |
|
|
<p class="text-xs text-gray-500">${formatDate(reply.createdAt)}</p> |
|
|
</div> |
|
|
${reply.userId === state.currentUser.id ? ` |
|
|
<div class="flex space-x-1"> |
|
|
<button class="edit-reply text-gray-500 hover:text-gray-700 text-xs"> |
|
|
<i class="fas fa-edit"></i> |
|
|
</button> |
|
|
<button class="delete-reply text-gray-500 hover:text-gray-700 text-xs"> |
|
|
<i class="fas fa-trash"></i> |
|
|
</button> |
|
|
</div> |
|
|
` : ''} |
|
|
</div> |
|
|
<p class="mt-1 text-xs">${reply.content}</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
repliesContainer.appendChild(replyElement); |
|
|
|
|
|
|
|
|
const editBtn = replyElement.querySelector('.edit-reply'); |
|
|
const deleteBtn = replyElement.querySelector('.delete-reply'); |
|
|
|
|
|
if (editBtn) { |
|
|
editBtn.addEventListener('click', () => editReply(postId, commentId, reply.id)); |
|
|
} |
|
|
|
|
|
if (deleteBtn) { |
|
|
deleteBtn.addEventListener('click', () => { |
|
|
if (confirm('Supprimer cette réponse ?')) { |
|
|
deleteReply(postId, commentId, reply.id); |
|
|
} |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
commentElement.appendChild(repliesContainer); |
|
|
} |
|
|
|
|
|
|
|
|
function renderBlockedUsers() { |
|
|
blockedUsersList.innerHTML = ''; |
|
|
|
|
|
const blockedUserIds = state.blockedUsers[state.currentUser.id] || []; |
|
|
|
|
|
if (blockedUserIds.length === 0) { |
|
|
noBlockedUsers.classList.remove('hidden'); |
|
|
return; |
|
|
} |
|
|
|
|
|
noBlockedUsers.classList.add('hidden'); |
|
|
|
|
|
blockedUserIds.forEach(userId => { |
|
|
const user = state.users.find(u => u.id === userId); |
|
|
if (!user) return; |
|
|
|
|
|
const blockedUserElement = document.createElement('div'); |
|
|
blockedUserElement.className = 'flex items-center justify-between p-2 bg-gray-100 rounded-lg'; |
|
|
blockedUserElement.dataset.userId = userId; |
|
|
|
|
|
blockedUserElement.innerHTML = ` |
|
|
<div class="flex items-center space-x-2"> |
|
|
<div class="w-8 h-8 rounded-full bg-gray-300 overflow-hidden"> |
|
|
<img src="${user.avatar}" alt="${user.fullName}" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
<div> |
|
|
<h4 class="font-medium">${user.fullName}</h4> |
|
|
<p class="text-xs text-gray-500">Bloqué</p> |
|
|
</div> |
|
|
</div> |
|
|
<button class="unblock-user text-blue-600 hover:text-blue-800"> |
|
|
Débloquer |
|
|
</button> |
|
|
`; |
|
|
|
|
|
blockedUsersList.appendChild(blockedUserElement); |
|
|
|
|
|
|
|
|
const unblockBtn = blockedUserElement.querySelector('.unblock-user'); |
|
|
unblockBtn.addEventListener('click', () => { |
|
|
unblockUser(userId); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function createPost() { |
|
|
const content = postContent.value.trim(); |
|
|
const mediaFile = postImage.files[0] || postVideo.files[0] || postAudio.files[0]; |
|
|
|
|
|
if (!content && !mediaFile) { |
|
|
alert('Veuillez ajouter du texte ou un média'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const newPost = { |
|
|
id: Date.now().toString(), |
|
|
userId: state.currentUser.id, |
|
|
content: content, |
|
|
createdAt: new Date().toISOString(), |
|
|
likes: [], |
|
|
comments: [] |
|
|
}; |
|
|
|
|
|
if (mediaFile) { |
|
|
|
|
|
|
|
|
const fileUrl = URL.createObjectURL(mediaFile); |
|
|
|
|
|
newPost.media = { |
|
|
type: mediaFile.type, |
|
|
url: fileUrl, |
|
|
name: mediaFile.name, |
|
|
size: mediaFile.size |
|
|
}; |
|
|
|
|
|
|
|
|
if (mediaFile.size > 20 * 1024 * 1024) { |
|
|
alert('Le fichier est trop volumineux (max 20MB)'); |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
state.posts.unshift(newPost); |
|
|
localStorage.setItem('posts', JSON.stringify(state.posts)); |
|
|
|
|
|
|
|
|
postContent.value = ''; |
|
|
postPreview.classList.add('hidden'); |
|
|
postPreview.innerHTML = ''; |
|
|
postImage.value = ''; |
|
|
postVideo.value = ''; |
|
|
postAudio.value = ''; |
|
|
|
|
|
|
|
|
renderPosts(); |
|
|
} |
|
|
|
|
|
|
|
|
function handleFileSelect(e) { |
|
|
const file = e.target.files[0]; |
|
|
if (!file) return; |
|
|
|
|
|
|
|
|
if (e.target.id === 'postImage') { |
|
|
postVideo.value = ''; |
|
|
postAudio.value = ''; |
|
|
} else if (e.target.id === 'postVideo') { |
|
|
postImage.value = ''; |
|
|
postAudio.value = ''; |
|
|
} else if (e.target.id === 'postAudio') { |
|
|
postImage.value = ''; |
|
|
postVideo.value = ''; |
|
|
} |
|
|
|
|
|
|
|
|
if (file.size > 20 * 1024 * 1024) { |
|
|
alert('Le fichier est trop volumineux (max 20MB)'); |
|
|
e.target.value = ''; |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
postPreview.classList.remove('hidden'); |
|
|
|
|
|
if (file.type.startsWith('image')) { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = (e) => { |
|
|
postPreview.innerHTML = ` |
|
|
<img src="${e.target.result}" alt="Preview" class="w-full rounded-lg"> |
|
|
`; |
|
|
}; |
|
|
reader.readAsDataURL(file); |
|
|
} else if (file.type.startsWith('video')) { |
|
|
const videoUrl = URL.createObjectURL(file); |
|
|
postPreview.innerHTML = ` |
|
|
<video controls class="w-full rounded-lg"> |
|
|
<source src="${videoUrl}" type="${file.type}"> |
|
|
Votre navigateur ne supporte pas les vidéos. |
|
|
</video> |
|
|
`; |
|
|
} else if (file.type.startsWith('audio')) { |
|
|
const audioUrl = URL.createObjectURL(file); |
|
|
postPreview.innerHTML = ` |
|
|
<audio controls class="w-full"> |
|
|
<source src="${audioUrl}" type="${file.type}"> |
|
|
Votre navigateur ne supporte pas les audios. |
|
|
</audio> |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function startAudioRecording() { |
|
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { |
|
|
navigator.mediaDevices.getUserMedia({ audio: true }) |
|
|
.then(stream => { |
|
|
state.audioRecorder = new MediaRecorder(stream); |
|
|
const audioChunks = []; |
|
|
|
|
|
state.audioRecorder.ondataavailable = e => { |
|
|
audioChunks.push(e.data); |
|
|
}; |
|
|
|
|
|
state.audioRecorder.onstop = () => { |
|
|
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); |
|
|
|
|
|
|
|
|
postPreview.classList.remove('hidden'); |
|
|
const audioUrl = URL.createObjectURL(audioBlob); |
|
|
postPreview.innerHTML = ` |
|
|
<audio controls class="w-full"> |
|
|
<source src="${audioUrl}" type="audio/wav"> |
|
|
Votre navigateur ne supporte pas les audios. |
|
|
</audio> |
|
|
`; |
|
|
|
|
|
|
|
|
const audioFile = new File([audioBlob], 'recording.wav', { type: 'audio/wav' }); |
|
|
|
|
|
|
|
|
const dataTransfer = new DataTransfer(); |
|
|
dataTransfer.items.add(audioFile); |
|
|
postAudio.files = dataTransfer.files; |
|
|
|
|
|
|
|
|
stream.getTracks().forEach(track => track.stop()); |
|
|
}; |
|
|
|
|
|
|
|
|
state.audioRecorder.start(); |
|
|
state.recordingStartTime = Date.now(); |
|
|
|
|
|
|
|
|
state.recordingTimer = setInterval(() => { |
|
|
const elapsed = Math.floor((Date.now() - state.recordingStartTime) / 1000); |
|
|
const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0'); |
|
|
const seconds = (elapsed % 60).toString().padStart(2, '0'); |
|
|
recordingTimer.textContent = `${minutes}:${seconds}`; |
|
|
}, 1000); |
|
|
|
|
|
|
|
|
audioRecorder.classList.remove('hidden'); |
|
|
}) |
|
|
.catch(err => { |
|
|
console.error('Error accessing microphone:', err); |
|
|
alert('Impossible d\'accéder au microphone'); |
|
|
}); |
|
|
} else { |
|
|
alert('L\'enregistrement audio n\'est pas supporté par votre navigateur'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function stopAudioRecording() { |
|
|
if (state.audioRecorder && state.audioRecorder.state !== 'inactive') { |
|
|
state.audioRecorder.stop(); |
|
|
clearInterval(state.recordingTimer); |
|
|
audioRecorder.classList.add('hidden'); |
|
|
recordingTimer.textContent = '00:00'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function toggleLike(postId) { |
|
|
const post = state.posts.find(p => p.id === postId); |
|
|
if (!post) return; |
|
|
|
|
|
if (!post.likes) { |
|
|
post.likes = []; |
|
|
} |
|
|
|
|
|
const likeIndex = post.likes.indexOf(state.currentUser.id); |
|
|
if (likeIndex === -1) { |
|
|
post.likes.push(state.currentUser.id); |
|
|
} else { |
|
|
post.likes.splice(likeIndex, 1); |
|
|
} |
|
|
|
|
|
localStorage.setItem('posts', JSON.stringify(state.posts)); |
|
|
renderPosts(); |
|
|
} |
|
|
|
|
|
|
|
|
function toggleComments(postElement, postId) { |
|
|
const commentsContainer = postElement.querySelector('.post-comments'); |
|
|
commentsContainer.classList.toggle('hidden'); |
|
|
|
|
|
if (!commentsContainer.classList.contains('hidden') && commentsContainer.innerHTML === '') { |
|
|
renderComments(postElement, postId); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function sharePost(postId) { |
|
|
const post = state.posts.find(p => p.id === postId); |
|
|
if (!post) return; |
|
|
|
|
|
const sharedPost = { |
|
|
id: Date.now().toString(), |
|
|
userId: state.currentUser.id, |
|
|
content: `Partagé depuis ${state.users.find(u => u.id === post.userId)?.fullName || 'un utilisateur'}: ${post.content}`, |
|
|
createdAt: new Date().toISOString(), |
|
|
sharedPostId: postId, |
|
|
likes: [], |
|
|
comments: [] |
|
|
}; |
|
|
|
|
|
if (post.media) { |
|
|
sharedPost.media = {...post.media}; |
|
|
} |
|
|
|
|
|
state.posts.unshift(sharedPost); |
|
|
localStorage.setItem('posts', JSON.stringify(state.posts)); |
|
|
|
|
|
renderPosts(); |
|
|
alert('Publication partagée !'); |
|
|
} |
|
|
|
|
|
|
|
|
function addComment(postId, content) { |
|
|
const post = state.posts.find(p => p.id === postId); |
|
|
if (!post) return; |
|
|
|
|
|
if (!post.comments) { |
|
|
post.comments = []; |
|
|
} |
|
|
|
|
|
const newComment = { |
|
|
id: Date.now().toString(), |
|
|
userId: state.currentUser.id, |
|
|
content: content, |
|
|
createdAt: new Date().toISOString(), |
|
|
likes: [], |
|
|
dislikes: [], |
|
|
replies: [] |
|
|
}; |
|
|
|
|
|
post.comments.push(newComment); |
|
|
localStorage.setItem('posts', JSON.stringify(state.posts)); |
|
|
|
|
|
|
|
|
const postElement = document.querySelector(`[data-post-id="${postId}"]`); |
|
|
if (postElement) { |
|
|
const commentsContainer = postElement.querySelector('.post-comments'); |
|
|
if (!commentsContainer.classList.contains('hidden')) { |
|
|
renderComments(postElement, postId); |
|
|
} |
|
|
|
|
|
|
|
|
const commentBtn = postElement.querySelector('.comment-btn'); |
|
|
if (commentBtn) { |
|
|
const countSpan = commentBtn.querySelector('span'); |
|
|
countSpan.textContent = post.comments.length; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function toggleCommentLike(postId, commentId) { |
|
|
const post = state.posts.find(p => p.id === postId); |
|
|
if (!post || !post.comments) return; |
|
|
|
|
|
const comment = post.comments.find(c => c.id === commentId); |
|
|
if (!comment) return; |
|
|
|
|
|
if (!comment.likes) { |
|
|
comment.likes = []; |
|
|
} |
|
|
|
|
|
const likeIndex = comment.likes.indexOf(state.currentUser.id); |
|
|
if (likeIndex === -1) { |
|
|
comment.likes.push(state.currentUser.id); |
|
|
|
|
|
|
|
|
if (comment.dislikes) { |
|
|
const dislikeIndex = comment.dislikes.indexOf(state.currentUser.id); |
|
|
if (dislikeIndex !== -1) { |
|
|
comment.dislikes.splice(dislikeIndex, 1); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
comment.likes.splice(likeIndex, 1); |
|
|
} |
|
|
|
|
|
localStorage.setItem('posts', JSON.stringify(state.posts)); |
|
|
|
|
|
|
|
|
const commentElement = document.querySelector(`[data-comment-id="${commentId}"]`); |
|
|
if (commentElement) { |
|
|
const likeBtn = commentElement.querySelector('.like-comment'); |
|
|
const dislikeBtn = commentElement.querySelector('.dislike-comment'); |
|
|
|
|
|
if (likeBtn) { |
|
|
likeBtn.classList.toggle('text-blue-600'); |
|
|
const countSpan = likeBtn.querySelector('span'); |
|
|
countSpan.textContent = comment.likes.length; |
|
|
} |
|
|
|
|
|
if (dislikeBtn) { |
|
|
dislikeBtn.classList.remove('text-red-600'); |
|
|
const countSpan = dislikeBtn.querySelector('span'); |
|
|
countSpan.textContent = comment.dislikes?.length || 0; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function toggleCommentDislike(postId, commentId) { |
|
|
const post = state.posts.find(p => p.id === postId); |
|
|
if (!post || !post.comments) return; |
|
|
|
|
|
const comment = post.comments.find(c => c.id === commentId); |
|
|
if (!comment) return; |
|
|
|
|
|
if (!comment.dislikes) { |
|
|
comment.dislikes = []; |
|
|
} |
|
|
|
|
|
const dislikeIndex = comment.dislikes.indexOf(state.currentUser.id); |
|
|
if (dislikeIndex === -1) { |
|
|
comment.dislikes.push(state.currentUser.id); |
|
|
|
|
|
|
|
|
if (comment.likes) { |
|
|
const likeIndex = comment.likes.indexOf(state.currentUser.id); |
|
|
if (likeIndex !== -1) { |
|
|
comment.likes.splice(likeIndex, 1); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
comment.dislikes.splice(dislikeIndex, 1); |
|
|
} |
|
|
|
|
|
localStorage.setItem('posts', JSON.stringify(state.posts)); |
|
|
|
|
|
|
|
|
const commentElement = document.querySelector(`[data-comment-id="${commentId}"]`); |
|
|
if (commentElement) { |
|
|
const dislikeBtn = commentElement.querySelector('.dislike-comment'); |
|
|
const likeBtn = commentElement.querySelector('.like-comment'); |
|
|
|
|
|
if (dislikeBtn) { |
|
|
dislikeBtn.classList.toggle('text-red-600'); |
|
|
const countSpan = dislikeBtn.querySelector('span'); |
|
|
countSpan.textContent = comment.dislikes.length; |
|
|
} |
|
|
|
|
|
if (likeBtn) { |
|
|
likeBtn.classList.remove('text-blue-600'); |
|
|
const countSpan = likeBtn.querySelector('span'); |
|
|
countSpan.textContent = comment.likes?.length || 0; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function toggleReplyInput(commentElement) { |
|
|
const replyInput = commentElement.querySelector('.comment-reply-input'); |
|
|
replyInput.classList.toggle('hidden'); |
|
|
|
|
|
if (!replyInput.classList.contains('hidden')) { |
|
|
const input = replyInput.querySelector('input'); |
|
|
input.focus(); |
|
|
|
|
|
input.addEventListener('keypress', (e) => { |
|
|
if (e.key === 'Enter' && input.value.trim()) { |
|
|
const postId = commentElement.closest('[data-post-id]').dataset.postId; |
|
|
const commentId = commentElement.dataset.commentId; |
|
|
addReply(postId, commentId, input.value.trim()); |
|
|
input.value = ''; |
|
|
replyInput.classList.add('hidden'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function addReply(postId, commentId, content |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Hubertoo/sbi-book" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |