Redesign chat UI and remove location sharing
Modernizes the chat and join pages with new styles, improved layouts, and enhanced user experience using custom CSS and FontAwesome icons. Removes location sharing functionality from both frontend and backend, including related templates and code. Adds connection status indicators, notification system, and keyboard shortcuts for better usability.
This commit is contained in:
@@ -4,60 +4,96 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Chat App</title>
|
<title>Modern Chat App - Chat</title>
|
||||||
<link rel="icon" href="./img/favicon.png">
|
<link rel="icon" href="./img/favicon.png">
|
||||||
<link rel="stylesheet" href="./css/styles.css">
|
<link rel="stylesheet" href="./css/styles.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="chat">
|
<div class="chat">
|
||||||
<div id="sidebar" class="chat__sidebar">
|
<div id="sidebar" class="chat__sidebar">
|
||||||
|
<!-- Sidebar content will be populated by JavaScript -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat__main">
|
<div class="chat__main">
|
||||||
<div id="messages" class="chat__messages"></div>
|
<div class="chat__header">
|
||||||
|
<div class="header-content">
|
||||||
|
<h2 id="room-name">Chat Room</h2>
|
||||||
|
<div class="connection-status">
|
||||||
|
<span class="status-indicator online"></span>
|
||||||
|
<span class="status-text">Connected</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="back-btn" onclick="location.href='/'">
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
Exit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="messages" class="chat__messages">
|
||||||
|
<div class="welcome-message">
|
||||||
|
<i class="fas fa-comments"></i>
|
||||||
|
<h3>Welcome to Chat!</h3>
|
||||||
|
<p>Start sending messages to begin chatting.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="compose">
|
<div class="compose">
|
||||||
<!-- <button id="increment">+1</button> -->
|
|
||||||
<form id="message-form">
|
<form id="message-form">
|
||||||
<input type="text" name="message" placeholder="Enter message" autocomplete="off">
|
<div class="input-wrapper">
|
||||||
<button type="submit">Send</button>
|
<input type="text" name="message" placeholder="Type your message..." autocomplete="off">
|
||||||
|
<button type="submit" id="send-btn">
|
||||||
|
<i class="fas fa-paper-plane"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<button id="send-location">Send Location</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<!-- Message Templates -->
|
||||||
<script id="message-template" type="text/html">
|
<script id="message-template" type="text/html">
|
||||||
<div class="message">
|
<div class="message {{#isOwn}}own-message{{/isOwn}}">
|
||||||
<p>
|
<div class="message__header">
|
||||||
<span class="message__name">{{username}}</span>
|
<span class="message__name">{{username}}</span>
|
||||||
<span class="message__meta">{{createdAt}}</span>
|
<span class="message__meta">{{createdAt}}</span>
|
||||||
</p>
|
</div>
|
||||||
|
<div class="message__content">
|
||||||
<p>{{message}}</p>
|
<p>{{message}}</p>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="locmessage-template" type="text/html">
|
|
||||||
<div class="message">
|
|
||||||
<p>
|
|
||||||
<span class="message__name">{{username}}</span>
|
|
||||||
<span class="message__meta">{{createdAt}}</span>
|
|
||||||
</p>
|
|
||||||
<p><a href="{{url}}" target="_blank" style="color: #7C5CBF;">My Current Location</a></p>
|
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id="sidebar-template" type="text/html">
|
<script id="sidebar-template" type="text/html">
|
||||||
<h2 class="room-title" >Room: {{room}}</h2>
|
<div class="sidebar-header">
|
||||||
<h3 class="list-title" >Users</h3>
|
<h2 class="room-title">
|
||||||
<ul class="users" >
|
<i class="fas fa-door-open"></i>
|
||||||
|
{{room}}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-content">
|
||||||
|
<h3 class="list-title">
|
||||||
|
<i class="fas fa-users"></i>
|
||||||
|
Active Users ({{users.length}})
|
||||||
|
</h3>
|
||||||
|
<ul class="users">
|
||||||
{{#users}}
|
{{#users}}
|
||||||
<li> {{username}} </li>
|
<li class="user-item">
|
||||||
|
<span class="online-indicator"></span>
|
||||||
|
<span class="user-name">{{username}}</span>
|
||||||
|
{{#isCurrentUser}}
|
||||||
|
<span class="current-user-badge">You</span>
|
||||||
|
{{/isCurrentUser}}
|
||||||
|
</li>
|
||||||
{{/users}}
|
{{/users}}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- External Libraries -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/3.0.1/mustache.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/3.0.1/mustache.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.6.0/qs.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.6.0/qs.min.js"></script>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* General Styles */
|
/* Modern Chat App Styles */
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -6,178 +6,808 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
:root {
|
||||||
font-size: 16px;
|
--primary-color: #667eea;
|
||||||
|
--primary-dark: #5a6fd8;
|
||||||
|
--secondary-color: #764ba2;
|
||||||
|
--accent-color: #f093fb;
|
||||||
|
--text-primary: #2d3748;
|
||||||
|
--text-secondary: #718096;
|
||||||
|
--bg-primary: #ffffff;
|
||||||
|
--bg-secondary: #f7fafc;
|
||||||
|
--bg-dark: #2d3748;
|
||||||
|
--border-color: #e2e8f0;
|
||||||
|
--success-color: #48bb78;
|
||||||
|
--error-color: #f56565;
|
||||||
|
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
--border-radius: 12px;
|
||||||
|
--border-radius-sm: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
html {
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
line-height: 1.4;
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
color: #333333;
|
line-height: 1.6;
|
||||||
font-family: Helvetica, Arial, sans-serif;
|
color: var(--text-primary);
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-bottom: 16px;
|
font-size: 2.5rem;
|
||||||
|
background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
h2 {
|
||||||
display: block;
|
font-size: 1.875rem;
|
||||||
font-size: 14px;
|
color: var(--text-primary);
|
||||||
margin-bottom: 8px;
|
}
|
||||||
color: #777;
|
|
||||||
|
/* Form Elements */
|
||||||
|
input, button, textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
border: 1px solid #eeeeee;
|
border: 2px solid var(--border-color);
|
||||||
padding: 12px;
|
padding: 1rem 1.25rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 12px;
|
padding: 1rem 2rem;
|
||||||
background: #7C5CBF;
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 16px;
|
font-weight: 600;
|
||||||
transition: background .3s ease;
|
font-size: 1rem;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover::before {
|
||||||
|
left: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background: #6b47b8;
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:disabled {
|
button:disabled {
|
||||||
cursor: default;
|
cursor: not-allowed;
|
||||||
background: #7c5cbf94;
|
opacity: 0.6;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Join Page Styles */
|
/* Join Page Styles */
|
||||||
|
|
||||||
.centered-form {
|
.centered-form {
|
||||||
background: #333744;
|
min-height: 100vh;
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 2rem;
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered-form::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="50" cy="10" r="0.5" fill="rgba(255,255,255,0.1)"/><circle cx="10" cy="60" r="0.5" fill="rgba(255,255,255,0.1)"/><circle cx="90" cy="40" r="0.5" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
|
||||||
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centered-form__box {
|
.centered-form__box {
|
||||||
box-shadow: 0px 0px 17px 1px #1D1F26;
|
background: var(--bg-primary);
|
||||||
background: #F7F7FA;
|
padding: 3rem;
|
||||||
padding: 24px;
|
border-radius: var(--border-radius);
|
||||||
width: 250px;
|
box-shadow: var(--shadow-lg);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.centered-form button {
|
.centered-form__box h1 {
|
||||||
width: 100%;
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered-form form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centered-form input {
|
.centered-form input {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered-form button {
|
||||||
|
margin-top: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: 1.25rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat Page Layout */
|
/* Chat Page Layout */
|
||||||
|
|
||||||
.chat {
|
.chat {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
background: var(--bg-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__sidebar {
|
.chat__sidebar {
|
||||||
height: 100vh;
|
width: 300px;
|
||||||
|
background: var(--bg-dark);
|
||||||
color: white;
|
color: white;
|
||||||
background: #333744;
|
|
||||||
width: 225px;
|
|
||||||
overflow-y: scroll
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Chat styles */
|
|
||||||
|
|
||||||
.chat__main {
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: 100vh;
|
border-right: 1px solid var(--border-color);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat__main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar Styles */
|
||||||
|
.sidebar-header {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||||
|
padding: 2rem 1.5rem 1.5rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-title i {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-title {
|
||||||
|
padding: 1.5rem 1.5rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-title i {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users {
|
||||||
|
list-style: none;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
border-left: 3px solid var(--primary-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
flex: 1;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-user-badge {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Messages Area */
|
||||||
.chat__messages {
|
.chat__messages {
|
||||||
flex-grow: 1;
|
flex: 1;
|
||||||
padding: 24px 24px 0 24px;
|
padding: 2rem;
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Message Styles */
|
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
margin-bottom: 16px;
|
background: var(--bg-primary);
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
border-left: 4px solid var(--primary-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
max-width: 80%;
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.own-message {
|
||||||
|
align-self: flex-end;
|
||||||
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||||
|
color: white;
|
||||||
|
border-left-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.own-message .message__name,
|
||||||
|
.message.own-message .message__meta {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.own-message .message__content p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.message:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message__name {
|
.message__name {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
color: var(--primary-color);
|
||||||
margin-right: 8px;
|
font-size: 0.875rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message__meta {
|
.message__meta {
|
||||||
color: #777;
|
color: var(--text-secondary);
|
||||||
font-size: 14px;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message a {
|
.message__content {
|
||||||
color: #0070CC;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Message Composition Styles */
|
.message__content p {
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Logo and Header Styles */
|
||||||
|
.logo-container {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
animation: bounce 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group.focused label {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group label i {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.features {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature i {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chat Header */
|
||||||
|
.chat__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--success-color);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator.offline {
|
||||||
|
background: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
background: linear-gradient(135deg, var(--error-color), #e53e3e);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
background: linear-gradient(135deg, #e53e3e, var(--error-color));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Welcome Message */
|
||||||
|
.welcome-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
animation: fadeIn 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message i {
|
||||||
|
font-size: 3rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message h3 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compose Area */
|
||||||
.compose {
|
.compose {
|
||||||
display: flex;
|
padding: 1.5rem 2rem;
|
||||||
flex-shrink: 0;
|
background: var(--bg-primary);
|
||||||
margin-top: 16px;
|
border-top: 1px solid var(--border-color);
|
||||||
padding: 24px;
|
box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose form {
|
.input-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
gap: 1rem;
|
||||||
margin-right: 16px;
|
margin-bottom: 1rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose input {
|
.input-wrapper:focus-within {
|
||||||
border: 1px solid #eeeeee;
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper input:focus {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper button {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 20%, 50%, 80%, 100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 10px rgba(72, 187, 120, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(72, 187, 120, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes typing {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.chat {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat__sidebar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px;
|
height: auto;
|
||||||
margin: 0 16px 0 0;
|
max-height: 200px;
|
||||||
flex-grow: 1;
|
}
|
||||||
|
|
||||||
|
.centered-form__box {
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compose form {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose button {
|
@media (max-width: 480px) {
|
||||||
font-size: 14px;
|
.centered-form {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered-form__box {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat__messages {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compose {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat Sidebar Styles */
|
/* Scrollbar Styling */
|
||||||
|
::-webkit-scrollbar {
|
||||||
.room-title {
|
width: 8px;
|
||||||
font-weight: 400;
|
|
||||||
font-size: 22px;
|
|
||||||
background: #2c2f3a;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-title {
|
::-webkit-scrollbar-track {
|
||||||
font-weight: 500;
|
background: var(--bg-secondary);
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
padding: 12px 24px 0 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.users {
|
::-webkit-scrollbar-thumb {
|
||||||
list-style-type: none;
|
background: var(--primary-color);
|
||||||
font-weight: 300;
|
border-radius: 4px;
|
||||||
padding: 12px 24px 0 24px;
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading States */
|
||||||
|
.loading {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Success/Error States */
|
||||||
|
.success {
|
||||||
|
border-left-color: var(--success-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
border-left-color: var(--error-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typing Indicator */
|
||||||
|
.typing-indicator {
|
||||||
|
padding: 1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Online Status */
|
||||||
|
.online-indicator {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--success-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notification System */
|
||||||
|
.notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
border-left: 4px solid var(--primary-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-success {
|
||||||
|
border-left-color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-error {
|
||||||
|
border-left-color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-warning {
|
||||||
|
border-left-color: #f6ad55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-info {
|
||||||
|
border-left-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification i:first-child {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-success i:first-child {
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-error i:first-child {
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-warning i:first-child {
|
||||||
|
color: #f6ad55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-info i:first-child {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification button:hover {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 10px rgba(72, 187, 120, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(72, 187, 120, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,25 +4,87 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Chat App</title>
|
<title>Modern Chat App - Join Chat</title>
|
||||||
<link rel="icon" href="./img/favicon.png">
|
<link rel="icon" href="./img/favicon.png">
|
||||||
<link rel="stylesheet" href="./css/styles.css">
|
<link rel="stylesheet" href="./css/styles.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="centered-form">
|
<div class="centered-form">
|
||||||
<div class="centered-form__box">
|
<div class="centered-form__box">
|
||||||
<h1>Join</h1>
|
<div class="logo-container">
|
||||||
<form action="./chat.html">
|
<i class="fas fa-comments logo-icon"></i>
|
||||||
<label>Display Name</label>
|
<h1>Chat App</h1>
|
||||||
<input type="text" name="username" required>
|
<p class="subtitle">Real-time chat experience</p>
|
||||||
<label>Room</label>
|
|
||||||
<input type="text" name="room" required>
|
|
||||||
<button>Join</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form action="./chat.html" id="join-form">
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="username">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<input type="text" name="username" id="username" required
|
||||||
|
placeholder="Enter your username" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="room">
|
||||||
|
<i class="fas fa-door-open"></i>
|
||||||
|
Room Name
|
||||||
|
</label>
|
||||||
|
<input type="text" name="room" id="room" required
|
||||||
|
placeholder="Enter room name to join" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" id="join-btn">
|
||||||
|
<i class="fas fa-sign-in-alt"></i>
|
||||||
|
Join Chat
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Form validation and animations
|
||||||
|
document.getElementById('join-form').addEventListener('submit', function(e) {
|
||||||
|
const username = document.getElementById('username').value.trim();
|
||||||
|
const room = document.getElementById('room').value.trim();
|
||||||
|
const button = document.getElementById('join-btn');
|
||||||
|
|
||||||
|
if (!username || !room) {
|
||||||
|
e.preventDefault();
|
||||||
|
button.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Please fill all fields';
|
||||||
|
button.style.background = 'linear-gradient(135deg, #f56565, #e53e3e)';
|
||||||
|
setTimeout(() => {
|
||||||
|
button.innerHTML = '<i class="fas fa-sign-in-alt"></i> Join Chat';
|
||||||
|
button.style.background = 'linear-gradient(135deg, #667eea, #764ba2)';
|
||||||
|
}, 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Joining...';
|
||||||
|
button.disabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Input focus animations
|
||||||
|
const inputs = document.querySelectorAll('input');
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.addEventListener('focus', function() {
|
||||||
|
this.parentElement.classList.add('focused');
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('blur', function() {
|
||||||
|
if (!this.value) {
|
||||||
|
this.parentElement.classList.remove('focused');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
// Socket.io bağlantısı - uzak sunucu için
|
// Socket.io connection - for remote server
|
||||||
const socket = io(window.location.origin);
|
const socket = io(window.location.origin);
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
const $messageForm = document.querySelector("#message-form");
|
const $messageForm = document.querySelector("#message-form");
|
||||||
const $messageFormInput = $messageForm.querySelector("input");
|
const $messageFormInput = $messageForm.querySelector("input");
|
||||||
const $messageFormButton = $messageForm.querySelector("button");
|
const $messageFormButton = $messageForm.querySelector("#send-btn");
|
||||||
const $sendLocationButton = document.querySelector("#send-location");
|
|
||||||
const $messages = document.querySelector("#messages");
|
const $messages = document.querySelector("#messages");
|
||||||
|
const $roomName = document.querySelector("#room-name");
|
||||||
|
const $connectionStatus = document.querySelector(".status-indicator");
|
||||||
|
const $statusText = document.querySelector(".status-text");
|
||||||
|
|
||||||
// Templates
|
// Templates
|
||||||
|
|
||||||
const messageTemplate = document.querySelector("#message-template").innerHTML;
|
const messageTemplate = document.querySelector("#message-template").innerHTML;
|
||||||
const locationTemplate = document.querySelector(
|
|
||||||
"#locmessage-template"
|
|
||||||
).innerHTML;
|
|
||||||
const sidebarTemplate = document.querySelector("#sidebar-template").innerHTML;
|
const sidebarTemplate = document.querySelector("#sidebar-template").innerHTML;
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
@@ -21,11 +19,39 @@ const { username, room } = Qs.parse(location.search, {
|
|||||||
ignoreQueryPrefix: true,
|
ignoreQueryPrefix: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Connection status management
|
||||||
|
let isConnected = true;
|
||||||
|
|
||||||
|
socket.on('connect', () => {
|
||||||
|
isConnected = true;
|
||||||
|
updateConnectionStatus(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
isConnected = false;
|
||||||
|
updateConnectionStatus(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateConnectionStatus(connected) {
|
||||||
|
if (connected) {
|
||||||
|
$connectionStatus.classList.remove('offline');
|
||||||
|
$connectionStatus.classList.add('online');
|
||||||
|
$statusText.textContent = 'Connected';
|
||||||
|
} else {
|
||||||
|
$connectionStatus.classList.remove('online');
|
||||||
|
$connectionStatus.classList.add('offline');
|
||||||
|
$statusText.textContent = 'Disconnected';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto scroll functionality
|
||||||
const autoScroll = () => {
|
const autoScroll = () => {
|
||||||
// New message element
|
// New message element
|
||||||
const $newMessage = $messages.lastElementChild;
|
const $newMessage = $messages.lastElementChild;
|
||||||
|
|
||||||
// hight of the new message
|
if (!$newMessage) return;
|
||||||
|
|
||||||
|
// height of the new message
|
||||||
const newMessageStyle = getComputedStyle($newMessage);
|
const newMessageStyle = getComputedStyle($newMessage);
|
||||||
const newMessageMargin = parseInt(newMessageStyle.marginBottom);
|
const newMessageMargin = parseInt(newMessageStyle.marginBottom);
|
||||||
const newMessageHeight = $newMessage.offsetHeight + newMessageMargin;
|
const newMessageHeight = $newMessage.offsetHeight + newMessageMargin;
|
||||||
@@ -44,107 +70,164 @@ const autoScroll = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// server (emit) -> client (receive) - countUpdated
|
// Message handling
|
||||||
// client (emit) -> server (receive) - increment
|
|
||||||
|
|
||||||
socket.on("message", (message) => {
|
socket.on("message", (message) => {
|
||||||
// console.log(message);
|
const isOwnMessage = message.username === username;
|
||||||
const html = Mustache.render(messageTemplate, {
|
const html = Mustache.render(messageTemplate, {
|
||||||
username: message.username,
|
username: message.username,
|
||||||
message: message.text,
|
message: message.text,
|
||||||
createdAt: moment(message.createdAt).format("h:mm a"),
|
createdAt: moment(message.createdAt).format("HH:mm"),
|
||||||
|
isOwn: isOwnMessage
|
||||||
});
|
});
|
||||||
$messages.insertAdjacentHTML("beforeend", html);
|
|
||||||
autoScroll();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("locationMessage", (url) => {
|
// Remove welcome message if it exists
|
||||||
// console.log(url.username);
|
const welcomeMessage = $messages.querySelector('.welcome-message');
|
||||||
const html = Mustache.render(locationTemplate, {
|
if (welcomeMessage) {
|
||||||
username: url.username,
|
welcomeMessage.remove();
|
||||||
url: url.url,
|
}
|
||||||
createdAt: moment(url.createdAt).format("h:mm a"),
|
|
||||||
});
|
|
||||||
$messages.insertAdjacentHTML("beforeend", html);
|
$messages.insertAdjacentHTML("beforeend", html);
|
||||||
autoScroll();
|
autoScroll();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("roomData", ({ room, users }) => {
|
socket.on("roomData", ({ room, users }) => {
|
||||||
|
const usersWithCurrent = users.map(user => ({
|
||||||
|
...user,
|
||||||
|
isCurrentUser: user.username === username
|
||||||
|
}));
|
||||||
|
|
||||||
const html = Mustache.render(sidebarTemplate, {
|
const html = Mustache.render(sidebarTemplate, {
|
||||||
room: room,
|
room: room,
|
||||||
users: users,
|
users: usersWithCurrent,
|
||||||
});
|
});
|
||||||
document.querySelector("#sidebar").innerHTML = html;
|
document.querySelector("#sidebar").innerHTML = html;
|
||||||
|
$roomName.textContent = room;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Form submission
|
||||||
$messageForm.addEventListener("submit", (e) => {
|
$messageForm.addEventListener("submit", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// disable form after submit
|
// disable form after submit
|
||||||
$messageFormButton.setAttribute("disabled", "disabled");
|
$messageFormButton.setAttribute("disabled", "disabled");
|
||||||
|
$messageFormButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||||
|
|
||||||
//disable
|
const message = $messageFormInput.value.trim();
|
||||||
|
|
||||||
const message = $messageFormInput.value;
|
|
||||||
|
|
||||||
if (message === "") {
|
if (message === "") {
|
||||||
// enable form after submit
|
// enable form after submit
|
||||||
$messageFormButton.removeAttribute("disabled");
|
$messageFormButton.removeAttribute("disabled");
|
||||||
|
$messageFormButton.innerHTML = '<i class="fas fa-paper-plane"></i>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.emit("sendMessage", message, (error) => {
|
socket.emit("sendMessage", message, (error) => {
|
||||||
// enable form after submit
|
// enable form after submit
|
||||||
$messageFormButton.removeAttribute("disabled");
|
$messageFormButton.removeAttribute("disabled");
|
||||||
|
$messageFormButton.innerHTML = '<i class="fas fa-paper-plane"></i>';
|
||||||
// clear input
|
// clear input
|
||||||
$messageFormInput.value = "";
|
$messageFormInput.value = "";
|
||||||
// focus input
|
// focus input
|
||||||
$messageFormInput.focus();
|
$messageFormInput.focus();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
showNotification(error, 'error');
|
||||||
return console.log(error);
|
return console.log(error);
|
||||||
}
|
}
|
||||||
// console.log("Message Delivered");
|
|
||||||
});
|
showNotification('Message sent!', 'success');
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector("#send-location").addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!navigator.geolocation) {
|
|
||||||
return alert("Geolocation is not supported by your browser");
|
|
||||||
}
|
|
||||||
|
|
||||||
navigator.permissions.query({ name: "geolocation" }).then((res) => {
|
|
||||||
// console.log(res);
|
|
||||||
if (res.state === "denied") {
|
|
||||||
return alert("Please allow permission to send location!");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
navigator.geolocation.getCurrentPosition((position) => {
|
|
||||||
// console.log(position);
|
|
||||||
$sendLocationButton.setAttribute("disabled", "disabled");
|
|
||||||
|
|
||||||
socket.emit(
|
|
||||||
"sendLocation",
|
|
||||||
{
|
|
||||||
Latitude: position.coords.latitude,
|
|
||||||
Longitude: position.coords.longitude,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
$sendLocationButton.removeAttribute("disabled");
|
|
||||||
// console.log("Location Shared");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Join room
|
||||||
socket.emit("join", { username, room }, (error) => {
|
socket.emit("join", { username, room }, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
alert(error);
|
showNotification(error, 'error');
|
||||||
|
setTimeout(() => {
|
||||||
location.href = "/";
|
location.href = "/";
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notification system
|
||||||
|
function showNotification(message, type = 'info') {
|
||||||
|
// Remove existing notifications
|
||||||
|
const existingNotifications = document.querySelectorAll('.notification');
|
||||||
|
existingNotifications.forEach(notification => notification.remove());
|
||||||
|
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.className = `notification notification-${type}`;
|
||||||
|
notification.innerHTML = `
|
||||||
|
<i class="fas fa-${getNotificationIcon(type)}"></i>
|
||||||
|
<span>${message}</span>
|
||||||
|
<button onclick="this.parentElement.remove()">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
// Auto remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (notification.parentElement) {
|
||||||
|
notification.remove();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNotificationIcon(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'success': return 'check-circle';
|
||||||
|
case 'error': return 'exclamation-circle';
|
||||||
|
case 'warning': return 'exclamation-triangle';
|
||||||
|
default: return 'info-circle';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyboard shortcuts
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
// Ctrl/Cmd + Enter to send message
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
||||||
|
$messageForm.dispatchEvent(new Event('submit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape to clear input
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
$messageFormInput.value = '';
|
||||||
|
$messageFormInput.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Input focus management
|
||||||
|
$messageFormInput.addEventListener('focus', () => {
|
||||||
|
$messageFormInput.parentElement.classList.add('focused');
|
||||||
|
});
|
||||||
|
|
||||||
|
$messageFormInput.addEventListener('blur', () => {
|
||||||
|
if (!$messageFormInput.value) {
|
||||||
|
$messageFormInput.parentElement.classList.remove('focused');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Page visibility API for better UX
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
document.title = '💬 Chat App - Background';
|
||||||
|
} else {
|
||||||
|
document.title = 'Chat App - Chat';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prevent form submission on Enter if Shift is held (for multi-line support)
|
||||||
|
$messageFormInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter' && e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
// Insert new line
|
||||||
|
const start = e.target.selectionStart;
|
||||||
|
const end = e.target.selectionEnd;
|
||||||
|
const value = e.target.value;
|
||||||
|
e.target.value = value.substring(0, start) + '\n' + value.substring(end);
|
||||||
|
e.target.selectionStart = e.target.selectionEnd = start + 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// document.querySelector("#increment").addEventListener("click", (e) => {
|
|
||||||
// console.log("clicked");
|
|
||||||
// socket.emit("increment");
|
|
||||||
// });
|
|
||||||
|
|||||||
16
src/index.js
16
src/index.js
@@ -14,7 +14,6 @@ const socketio = require("socket.io");
|
|||||||
const Filter = require("bad-words");
|
const Filter = require("bad-words");
|
||||||
const {
|
const {
|
||||||
generateMessage,
|
generateMessage,
|
||||||
generateLocationMessage,
|
|
||||||
} = require("./utils/messages");
|
} = require("./utils/messages");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -114,22 +113,7 @@ io.on("connection", (socket) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("sendLocation", (coords, callback) => {
|
|
||||||
const user = getUser(socket.id);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return callback("You are not authenticated");
|
|
||||||
}
|
|
||||||
|
|
||||||
io.to(user.room).emit(
|
|
||||||
"locationMessage",
|
|
||||||
generateLocationMessage(
|
|
||||||
user.username,
|
|
||||||
`https://google.com/maps?q=${coords.Latitude},${coords.Longitude}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// start the server
|
// start the server
|
||||||
|
|||||||
@@ -10,15 +10,6 @@ const generateMessage = (username, text) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateLocationMessage = (username, url) => {
|
|
||||||
return {
|
|
||||||
username: username,
|
|
||||||
url: url,
|
|
||||||
createdAt: new Date().getTime(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
generateMessage,
|
generateMessage,
|
||||||
generateLocationMessage,
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user