From 7eba84b6ec157b7331ad4f1c0c70079ce94e2f07 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Fri, 6 Jun 2025 10:03:27 +1000 Subject: [PATCH] Rewrite the web client to look sleeker --- client-web/index.css | 1144 ++++++++++++++++++++++++++++------------- client-web/index.html | 225 +++++--- client-web/index.js | 1089 +++++++++++++++++++++++++++------------ 3 files changed, 1706 insertions(+), 752 deletions(-) diff --git a/client-web/index.css b/client-web/index.css index 32410c8..d83ede0 100644 --- a/client-web/index.css +++ b/client-web/index.css @@ -1,419 +1,825 @@ @font-face { - font-family: "inter"; + font-family: "Inter"; src: url("InterVariable.ttf"); } -a { - color: white; +:root { + --primary-color: #5865f2; + --success-color: #3ba55c; + --danger-color: #ed4245; + --background-dark: #36393f; + --background-darker: #2f3136; + --background-light: #40444b; + --background-lightest: #4f545c; + --background-overlay: rgba(0, 0, 0, 0.6); + --text-normal: #dcddde; + --text-muted: #a3a6aa; + --text-link: #00aff4; + --text-positive: #3ba55c; + --scrollbar-thin-thumb: #202225; + --scrollbar-thin-track: transparent; + --sidebar-width: 240px; + --users-panel-width: 240px; + --chat-input-height: 68px; + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 16px; + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + --shadow-md: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + --shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23); + --animation-duration: 0.2s; } -input { - color: white; - background: rgba(0, 0, 0, 0.5); - border: none; - border-radius: 10px; - padding: 5px; - font-family: "inter"; - margin: 10px; -} - -.tiny { - font-size: 5pt; -} - -h1 { - font-size: 35pt; -} - -h2 { - font-size: 20pt; -} - -h3 { - font-size: 15pt; -} - -p { - font-size: 12pt; -} - -#messagebox { - overflow-y: auto; - border: 0px; - padding: 20px; - margin: 10px 10px; - flex-grow: 1; -} - -.bluebutton { - background: rgba(0, 0, 255, 0.3); -} -.greenbutton { - background: rgba(0, 255, 0, 0.3); -} -.redbutton { - background: rgba(255, 0, 0, 0.3); -} -.hidden { - display: none; -} -html, body { - height: 100%; +* { margin: 0; padding: 0; - overflow: hidden; box-sizing: border-box; } -body { - color: white; - background: rgb(231,255,68); - background: linear-gradient(336deg, rgba(231,255,68,0.67) 0%, rgba(73,255,145,0.67) 43%, rgba(104,79,255,1) 100%); - font-family: inter; - background-attachment: fixed; - padding: 20px; - display: flex; - flex-direction: column; - align-items: center; -} - -.section { - background-color: rgba(0, 0, 0, 0.1); - border-radius: 10px; - padding: 5px; - width: calc(100vw - 40px); - height: calc(100vh - 40px); - box-sizing: border-box; -} - -.section h2 { - margin: 20px; -} - -.box { - color: white; - background: rgba(0, 0, 0, 0.5); - border-radius: 10px; - margin: 10px auto; - padding: 20px; - max-width: 400px; - width: 100%; - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: center; -} - -.box p { - margin: 10px; -} -.box h3 { - margin: 10px; -} -.box img { - margin: 10px; -} -.box button { - margin: 10px; -} - -#messaging .box { - max-width: none; +html, body { height: 100%; - position: relative; + width: 100%; + overflow: hidden; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + color: var(--text-normal); + background-color: var(--background-dark); } -#messagebox { - overflow-y: auto; - border: 0px; - padding: 20px; - margin: 10px 0; - flex-grow: 1; +/* Custom Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--background-lightest); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} + +.hidden { + display: none !important; +} + +/* Login Page */ +#login-container { + height: 100vh; + width: 100vw; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #721c7a, #612bd4, #3aa5c5, #30cf7f); + background-size: 400% 400%; + animation: gradient 120s ease infinite; +} + +@keyframes gradient { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +.login-card { width: 100%; - box-sizing: border-box; + max-width: 480px; + background-color: var(--background-dark); + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + overflow: hidden; + padding: 2rem; +} + +.login-header { + text-align: center; + margin-bottom: 2rem; +} + +.login-header h2 { + font-size: 2rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text-normal); +} + +.login-header p { + color: var(--text-muted); + font-size: 0.9rem; +} + +.login-form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.input-group { + position: relative; + display: flex; + align-items: center; +} + +.input-group i { + position: absolute; + left: 12px; + color: var(--text-muted); + font-size: 1rem; +} + +.input-group input { + width: 100%; + padding: 12px 12px 12px 40px; + background-color: var(--background-darker); + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: var(--radius-sm); + color: var(--text-normal); + font-size: 1rem; + transition: border-color var(--animation-duration) ease; +} + +.input-group input:focus { + outline: none; + border-color: var(--primary-color); +} + +.checkbox-group { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.5rem; +} + +.checkbox-group input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; +} + +.checkbox-group label { + color: var(--text-normal); + font-size: 0.9rem; + cursor: pointer; +} + +.login-buttons { + display: flex; + gap: 1rem; + margin-top: 0.5rem; +} + +.server-options { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 1rem; +} + +.server-config { + margin-top: 1rem; + width: 100%; + display: none; +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10px 16px; + border-radius: var(--radius-sm); + font-size: 0.9rem; + font-weight: 500; + cursor: pointer; + transition: background-color var(--animation-duration) ease; + border: none; + color: var(--text-normal); +} + +.btn-primary { + background-color: var(--primary-color); +} + +.btn-primary:hover { + background-color: #4752c4; +} + +.btn-success { + background-color: var(--success-color); +} + +.btn-success:hover { + background-color: #2d8a49; +} + +.btn-danger { + background-color: var(--danger-color); +} + +.btn-danger:hover { + background-color: #c7373a; +} + +.btn-text { + background-color: transparent; + color: var(--text-link); + padding: 5px 10px; +} + +.btn-text:hover { + text-decoration: underline; +} + +.btn-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 50%; + background-color: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + transition: all var(--animation-duration) ease; +} + +.btn-icon:hover { + color: var(--text-normal); + background-color: var(--background-lightest); +} + +/* App Layout */ +#app { + height: 100vh; + width: 100vw; + display: grid; + grid-template-columns: var(--sidebar-width) 1fr var(--users-panel-width); + grid-template-rows: 1fr; +} + +/* Sidebar */ +.sidebar { + grid-column: 1; + background-color: var(--background-darker); + height: 100%; + display: flex; + flex-direction: column; + border-right: 1px solid rgba(0, 0, 0, 0.2); +} + +.sidebar-header { + padding: 1rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); +} + +.sidebar-header h3 { + font-size: 1.2rem; + font-weight: 600; +} + +.room-list { + flex: 1; + overflow-y: auto; + padding: 1rem 0.5rem; +} + +.room-category { + margin-bottom: 1rem; +} + +.room-category-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem; + font-size: 0.8rem; + font-weight: 700; + text-transform: uppercase; + color: var(--text-muted); +} + +.room-item { + display: flex; + align-items: center; + padding: 0.5rem 0.75rem; + margin: 0.25rem 0; + border-radius: var(--radius-sm); + font-size: 1rem; + color: var(--text-muted); + cursor: pointer; + transition: all var(--animation-duration) ease; +} + +.room-item:hover { + background-color: var(--background-light); + color: var(--text-normal); +} + +.room-item.active { + background-color: var(--background-light); + color: var(--text-normal); + font-weight: 500; +} + +.room-item i { + margin-right: 0.5rem; + font-size: 1rem; +} + +.user-profile { + padding: 1rem; + display: flex; + align-items: center; + gap: 0.75rem; + background-color: rgba(0, 0, 0, 0.15); +} + +.user-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background-color: var(--primary-color); + display: flex; + align-items: center; + justify-content: center; + color: white; +} + +.user-info { + display: flex; + flex-direction: column; +} + +.user-status { + font-size: 0.75rem; + color: var(--text-muted); +} + +/* Chat Container */ +.chat-container { + grid-column: 2; + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.chat-header { + height: 48px; + padding: 0 1rem; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + background-color: var(--background-dark); + z-index: 10; +} + +.current-room { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1rem; + font-weight: 600; +} + +.current-room i { + color: var(--text-muted); +} + +.header-actions { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.chat-content { + flex: 1; + display: flex; + flex-direction: column; + /* background-color: var(--background-dark); + position: relative; + height: 100%;*/ + overflow: hidden; +} + +.messages { + flex: 1; + overflow-y: auto; + padding: 1rem; + display: flex; + flex-direction: column; + gap: 0.25rem; + min-height: 0; } .message { - text-align: left; - margin: 5px 0; - width: 100%; - display: flex; - flex-direction: column; - align-items: flex-start; -} - -.file { - float: left; - border-radius: 10px; - margin: 5px 0; -} - -.input-container { display: flex; - width: 100%; - gap: 10px; - box-sizing: border-box; + margin-bottom: 0.5rem; } -input { - color: white; - background: rgba(0, 0, 0, 0.5); - border: none; - border-radius: 10px; - padding: 5px 5px; - font-family: "inter"; - max-width: 300px; - width: 100%; - box-sizing: border-box; +.message-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: var(--background-light); + margin-right: 1rem; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-normal); + font-size: 1rem; +} + +.message-content { + flex: 1; +} + +.message-header { + display: flex; + align-items: baseline; + margin-bottom: 0.25rem; +} + +.message-author { + font-weight: 500; + margin-right: 0.5rem; +} + +.message-timestamp { + font-size: 0.75rem; + color: var(--text-muted); +} + +.message-text { + word-wrap: break-word; + line-height: 1.4; +} + +.system-message { + padding: 0.5rem 0; + color: var(--text-muted); + font-style: italic; + font-size: 0.9rem; + text-align: center; +} + +.history-message { + opacity: 0.7; +} + +.message.history-message .message-timestamp { + font-style: italic; +} + +.call-notification { + margin: 0.5rem 0; + padding: 0.75rem 1rem; + background-color: rgba(88, 101, 242, 0.15); + border-radius: var(--radius-sm); + color: var(--text-link); + cursor: pointer; + transition: background-color var(--animation-duration) ease; +} + +.call-notification:hover { + background-color: rgba(88, 101, 242, 0.25); +} + +.typing-indicator { + padding: 0.25rem 1rem; + font-size: 0.85rem; + color: var(--text-muted); + font-style: italic; +} + +.message-input-container { + padding: 0 1rem 1rem; + background-color: var(--background-dark); + border-top: 1px solid rgba(0, 0, 0, 0.2); + flex-shrink: 0; +} + +.message-actions { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.message-input-wrapper { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0 1rem; + background-color: var(--background-light); + border-radius: var(--radius-md); } #messageInput { - max-width: none; - flex-grow: 1; - margin: 0; -} - -.bluebutton, .greenbutton, .redbutton { - max-width: 200px; - width: auto; - margin: 0; - white-space: nowrap; - color: white; - font-size: 12pt; - font-family: inter; - border-radius: 10px; - border: none; - padding: 8px 12px; - max-width: none; - width: auto; - margin: 5px 0; - white-space: normal; - word-wrap: break-word; - text-align: center; - cursor: pointer; - -} -.bluebutton, .greenbutton, .redbutton:hover { - background: rgba(255, 255, 255, 0.1); - border-radius: 5px; -} -#users { - display: flex; - align-items: left; - justify-content: space-between; - width: 100%; -} - -#meeting { - cursor: pointer; - padding: 5px 10px; - margin-right: 10px; - align-items: right; -} - -.suttle { - text-align: left; - width: 100%; - padding-left: 10px; - margin-bottom: 5px; - font-size: 0.9em; - color: rgba(255, 255, 255, 0.7); -} - -#users { - position: relative; - width: 100%; -} - -#meeting { - position: absolute; - right: 10px; - top: 0; - cursor: pointer; - padding: 5px 10px; -} -.message.call-notification { - color: lightblue; -} - -#meet { - display: none; - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 1000; - background: rgba(0, 0, 0, 0.9); - padding: 20px; - border-radius: 10px; - width: 80%; - height: 80%; -} -.eggs-panel { - position: absolute; - right: -300px; - top: 0; - width: 300px; - height: 100%; - background: rgba(0, 0, 0, 0.5); - border-radius: 10px; - transition: right 0.3s ease; -} - -.eggs-panel.visible { - right: -310px; -} - -.eggs-list { - padding: 10px; -} - -.egg-item { - width: 100%; - padding: 10px; - margin: 5px 0; - background: rgba(0, 0, 0, 0.3); - border: none; - border-radius: 5px; - color: white; - font-family: "inter"; - cursor: pointer; -} - -.egg-item:hover { - background: rgba(255, 255, 255, 0.1); -} - -.egg-content { - padding: 10px; - height: calc(100% - 20px); - background: rgba(0, 0, 0, 0.3); - border-radius: 5px; - margin: 10px; -} -#messaging { - width: 100%; - height: 100%; -} - -.messaging-container { - display: flex; - flex-direction: row; - height: 100%; - position: relative; -} - -.box { flex: 1; - margin-right: 10px; + height: 44px; + background-color: transparent; + border: none; + color: var(--text-normal); + font-size: 1rem; + padding: 0; } -.eggs-panel { - width: 300px; - background: rgba(0, 0, 0, 0.5); - border-radius: 10px; - transition: transform 0.3s ease; - transform: translateX(100%); +#messageInput:focus { + outline: none; +} + +.send-button { + background-color: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + transition: color var(--animation-duration) ease; +} + +.send-button:hover { + color: var(--primary-color); +} + +/* Users Panel */ +.users-panel { + grid-column: 3; + background-color: var(--background-darker); height: 100%; + border-left: 1px solid rgba(0, 0, 0, 0.2); +} + +.users-panel-header { + padding: 1rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); +} + +.users-panel-header h3 { + font-size: 1rem; + font-weight: 600; + color: var(--text-muted); +} + +.users-list { + padding: 1rem; +} + +.user-item { + display: flex; + align-items: center; + padding: 0.5rem; + margin-bottom: 0.5rem; + border-radius: var(--radius-sm); + transition: background-color var(--animation-duration) ease; +} + +.user-item:hover { + background-color: var(--background-light); +} + +/* Eggs Panel */ +.eggs-panel { + position: fixed; + top: 0; + right: 0; + width: 300px; + height: 100vh; + background-color: var(--background-dark); + border-left: 1px solid rgba(0, 0, 0, 0.2); + z-index: 100; + display: flex; + flex-direction: column; + transform: translateX(100%); + transition: transform var(--animation-duration) ease; } .eggs-panel.visible { transform: translateX(0); } +.eggs-header { + padding: 1rem; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); +} + +.eggs-header h3 { + font-size: 1rem; + font-weight: 600; +} + .eggs-list { - padding: 10px; -} - -.egg-item { - width: 100%; - padding: 10px; - margin: 5px 0; - background: rgba(0, 0, 0, 0.3); - border: none; - border-radius: 5px; - color: white; - font-family: "inter"; - cursor: pointer; -} - -.egg-item:hover { - background: rgba(255, 255, 255, 0.1); -} - -.egg-content { - padding: 10px; - height: calc(100% - 20px); - background: rgba(0, 0, 0, 0.3); - border-radius: 5px; - margin: 10px; -} - -.room-list { - width: 200px; - background: rgba(0, 0, 0, 0.5); - border-radius: 10px; - margin-right: 10px; - padding: 10px; - height: 100%; + padding: 1rem; overflow-y: auto; } -.room-header { - font-weight: bold; - text-align: center; - padding: 5px; - margin-bottom: 10px; - border-bottom: 1px solid rgba(255, 255, 255, 0.3); -} - -.room-item { - padding: 10px; - margin: 5px 0; - border-radius: 5px; - cursor: pointer; - transition: background 0.2s; -} - -.room-item:hover { - background: rgba(255, 255, 255, 0.1); -} - -.room-item.active { - background: rgba(104, 79, 255, 0.3); - font-weight: bold; -} - -.create-room-button { +.egg-item { + display: flex; + align-items: center; + gap: 0.75rem; width: 100%; - padding: 8px; - margin-top: 10px; - background: rgba(73, 255, 145, 0.3); + padding: 0.75rem; + margin-bottom: 0.5rem; + background-color: var(--background-light); border: none; - border-radius: 5px; - color: white; + border-radius: var(--radius-sm); + color: var(--text-normal); + font-size: 0.9rem; + text-align: left; cursor: pointer; - transition: background 0.2s; + transition: background-color var(--animation-duration) ease; } -.create-room-button:hover { - background: rgba(73, 255, 145, 0.5); +.egg-item:hover { + background-color: var(--background-lightest); } -.room-title { - font-weight: bold; - font-size: 16px; - margin-bottom: 5px; - padding: 5px 10px; - background: rgba(0, 0, 0, 0.3); - border-radius: 5px; +.egg-content { + flex: 1; + padding: 1rem; + overflow-y: auto; } -.message.system-message { - color: rgba(73, 255, 145, 0.8); - font-style: italic; +/* Modal */ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: var(--background-overlay); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; } -.message.history-message { - color: rgba(255, 255, 255, 0.7); +.modal-content { + width: 100%; + max-width: 500px; + background-color: var(--background-dark); + border-radius: var(--radius-md); + overflow: hidden; +} + +.modal-header { + padding: 1rem; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); +} + +.modal-header h3 { + font-size: 1.1rem; + font-weight: 600; +} + +.modal-body { + padding: 1rem; +} + +#fileupload { + width: 100%; + padding: 1rem; + background-color: var(--background-light); + border-radius: var(--radius-sm); + margin-bottom: 1rem; + cursor: pointer; +} + +.upload-preview { + margin: 1rem 0; + max-height: 300px; + overflow: auto; + text-align: center; +} + +.upload-preview img { + max-width: 100%; + max-height: 200px; + border-radius: var(--radius-sm); +} + +/* Meet Container */ +.meet-container { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: var(--background-overlay); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; +} + +/* Responsive Design */ +@media (max-width: 992px) { + #app { + grid-template-columns: var(--sidebar-width) 1fr; + } + + .users-panel { + display: none; + } +} + +@media (max-width: 768px) { + #app { + grid-template-columns: 1fr; + } + + .sidebar { + position: fixed; + left: -100%; + width: var(--sidebar-width); + z-index: 100; + transition: left var(--animation-duration) ease; + } + + .sidebar.visible { + left: 0; + } + + .chat-header { + padding-left: 0.5rem; + } + + .menu-toggle { + display: block; + } +} + +/* File Message */ +.file-message { + display: flex; + align-items: center; + padding: 0.75rem; + background-color: var(--background-light); + border-radius: var(--radius-sm); + margin-top: 0.25rem; +} + +.file-icon { + margin-right: 0.75rem; + font-size: 1.5rem; + color: var(--text-link); +} + +.file-name { + flex: 1; + font-size: 0.9rem; +} + +.file-button { + padding: 0.4rem 0.75rem; + background-color: var(--primary-color); + border: none; + border-radius: var(--radius-sm); + color: white; + font-size: 0.8rem; + cursor: pointer; +} + +.file-button:hover { + background-color: #4752c4; +} + +/* Image Message */ +.image-message { + margin-top: 0.25rem; + max-width: 400px; +} + +.image-message img { + max-width: 100%; + border-radius: var(--radius-sm); + cursor: pointer; + transition: opacity var(--animation-duration) ease; +} + +.image-message img:hover { + opacity: 0.9; } diff --git a/client-web/index.html b/client-web/index.html index a2e21e4..0d36af5 100644 --- a/client-web/index.html +++ b/client-web/index.html @@ -1,68 +1,171 @@ - - - - - Chookchat - - - - - - - - - -
-
-
-

Chookchat

-
-
- - - + + + + + Chookchat + + + + + + + + + +
+ + + + + + + + + + diff --git a/client-web/index.js b/client-web/index.js index 927def5..b7f7602 100644 --- a/client-web/index.js +++ b/client-web/index.js @@ -4,34 +4,82 @@ let ws; let username; let password; let typingTimeout; -let typingPeople = new Array(); -let api; +let typingPeople = []; let currentRoom = "general"; let availableRooms = ["general"]; +let isMobile = window.innerWidth < 768; +let avatarColors = {}; -function resizeMessaging() { - const messagingDiv = document.getElementById('messaging'); - if (messagingDiv) { - messagingDiv.style.width = `${window.innerWidth - 40}px`; - messagingDiv.style.height = `${window.innerHeight - 40}px`; +// Initialize DOM elements when document is ready +document.addEventListener('DOMContentLoaded', function() { + // Mobile menu toggle + if (isMobile) { + const menuToggle = document.createElement('button'); + menuToggle.className = 'btn-icon menu-toggle'; + menuToggle.innerHTML = ''; + menuToggle.addEventListener('click', toggleSidebar); + + const chatHeader = document.querySelector('.chat-header'); + if (chatHeader) { + chatHeader.insertBefore(menuToggle, chatHeader.firstChild); + } + } + + // Add event listeners + document.getElementById('messageInput').addEventListener('keypress', (event) => { + if (event.key === 'Enter') { + sendMessage(); + } + }); + + document.getElementById('password').addEventListener('keypress', (event) => { + if (event.key === 'Enter') { + connect(); + } + }); + + document.getElementById('messageInput').addEventListener('input', startTypingIndicator); + + // File upload preview + const fileInput = document.getElementById('fileupload'); + if (fileInput) { + fileInput.addEventListener('change', previewFile); + } + + // Handle window resize + window.addEventListener('resize', () => { + isMobile = window.innerWidth < 768; + if (!isMobile) { + const sidebar = document.querySelector('.sidebar'); + if (sidebar) { + sidebar.classList.remove('visible'); + } + } + }); +}); + +// Toggle sidebar on mobile +function toggleSidebar() { + const sidebar = document.querySelector('.sidebar'); + if (sidebar) { + sidebar.classList.toggle('visible'); } } -resizeMessaging(); - -window.addEventListener('resize', resizeMessaging); - +// Show server config function showConfig() { - const serverconfig = document.getElementById('serverconfig') + const serverconfig = document.getElementById('serverconfig'); if (serverconfig) { - serverconfig.style.display = 'block'; + serverconfig.style.display = serverconfig.style.display === 'block' ? 'none' : 'block'; } } +// Hash password using MD5 function md5(string) { return CryptoJS.MD5(string).toString(); } +// Get WebSocket URL function getUrl() { const serverUrl = document.getElementById('serverUrl').value.trim(); const serverPort = document.getElementById('serverPort').value; @@ -42,6 +90,8 @@ function getUrl() { return `${protocol}://${cleanUrl}:${serverPort}/api/websocket`; } + +// Get signup URL function getSignupUrl() { const serverUrl = document.getElementById('serverUrl').value.trim(); const serverPort = document.getElementById('serverPort').value; @@ -52,6 +102,8 @@ function getSignupUrl() { return `${protocol}://${cleanUrl}:${serverPort}/api/createaccount/`; } + +// Get upload URL function getUploadUrl() { const serverUrl = document.getElementById('serverUrl').value.trim(); const serverPort = document.getElementById('serverPort').value; @@ -62,6 +114,8 @@ function getUploadUrl() { return `${protocol}://${cleanUrl}:${serverPort}/api/upload`; } + +// Get available rooms async function getRooms() { try { const serverUrl = document.getElementById('serverUrl').value.trim(); @@ -72,7 +126,7 @@ async function getRooms() { const url = `${protocol}://${cleanUrl}:${serverPort}/api/rooms`; const response = await fetch(url, { mode: "no-cors" - }); + }); const data = await response.json(); return JSON.parse(data.content); } catch (error) { @@ -81,6 +135,7 @@ async function getRooms() { } } +// Get room message history async function getRoomHistory(roomName) { try { const serverUrl = document.getElementById('serverUrl').value.trim(); @@ -91,7 +146,7 @@ async function getRoomHistory(roomName) { const url = `${protocol}://${cleanUrl}:${serverPort}/api/room/${roomName}/history`; const response = await fetch(url, { mode: "no-cors" - }); + }); const history = await response.text(); return history; } catch (error) { @@ -100,15 +155,16 @@ async function getRoomHistory(roomName) { } } +// Image mime types and extensions const imageMimeTypes = [ - 'image/webp', - 'image/tiff', - 'image/svg+xml', - 'image/png', - 'image/jpeg', - 'image/vnd.microsoft.icon', - 'image/gif', - 'image/bmp', + 'image/webp', + 'image/tiff', + 'image/svg+xml', + 'image/png', + 'image/jpeg', + 'image/vnd.microsoft.icon', + 'image/gif', + 'image/bmp', ]; const imageTypes = [ @@ -122,11 +178,13 @@ const imageTypes = [ 'bmp' ]; +// Check if file is an image function isImage(file) { const fileSplit = file.split("."); - return imageTypes.includes(fileSplit[fileSplit.length - 1]); + return imageTypes.includes(fileSplit[fileSplit.length - 1].toLowerCase()); } +// Connect to the server async function connect() { username = document.getElementById('username').value; password = document.getElementById('password').value; @@ -141,8 +199,14 @@ async function connect() { ws.close(); } - ws = new WebSocket(wsUrl); + // Show connecting state + const loginButton = document.querySelector('.btn-primary'); + if (loginButton) { + loginButton.innerHTML = ' Connecting...'; + loginButton.disabled = true; + } + ws = new WebSocket(wsUrl); var incorrectDetail = 0; ws.onopen = async () => { @@ -150,8 +214,12 @@ async function connect() { Notification.requestPermission(); } console.log('Connected!'); - document.getElementById('login').style.display = 'none'; - document.getElementById('messaging').style.display = 'block'; + + document.getElementById('login-container').style.display = 'none'; + document.getElementById('app').classList.remove('hidden'); + + // Set current username in UI + document.getElementById('current-username').textContent = username; try { availableRooms = await getRooms(); @@ -171,193 +239,405 @@ async function connect() { joinRoom("general"); - ws.onmessage = (event) => { - if (event.data === "ping") { - ws.send("pong"); - return; - } - const message = JSON.parse(event.data); - if (message.type == "error") { - if (message.username == "system") { - if (message.content == "invalid-token") { - alert("Your password is incorrect! Please try putting in your password right."); - incorrectDetail = 1; - location.reload(); - } - if (message.content == "unknown-account") { - alert("That username isn't on the server. Maybe try registering?"); - incorrectDetail = 1; - location.reload(); - } - if (message.content == "banned") { - alert("kiddo you're banned lol what did you do to get banned lmaooo"); - incorrectDetail = 1; - location.reload(); - } - } - } - else if (message.type == "typing" && message.content == "1") { - if (username !== message.username && !typingPeople.includes(message.username)) { - typingPeople.push(message.username); - updatePeopleTyping(); - } - return; - } - else if (message.type == "typing" && message.content == "0") { - if (username !== message.username && typingPeople.includes(message.username)) { - const index = typingPeople.indexOf(message.username); - typingPeople.splice(index, 1); - updatePeopleTyping(); - } - return; - } - else if (message.type == "users" && message.username == "system") { - usersDiv = document.getElementById("users"); - if (usersDiv) { - usersDiv.textContent = `Online users: ${message.content}` - } - return; - } - else if (message.type == "roomUsers" && message.username == "system") { - const usersInRoom = message.content; - const roomName = message.room; - usersDiv = document.getElementById("users"); - if (usersDiv) { - usersDiv.textContent = `Users in ${roomName}: ${usersInRoom}`; - if (roomName !== currentRoom) { - currentRoom = roomName; - updateCurrentRoomDisplay(); - } - } - return; - } - else if (message.type == "roomsList" && message.username == "system") { - try { - availableRooms = JSON.parse(message.content); - updateRoomList(); - } catch (error) { - console.error('Error parsing rooms list:', error); - } - return; - } - else if (message.type == "roomCreated" || message.type == "roomJoin") { - if (message.room) { - currentRoom = message.room; - updateCurrentRoomDisplay(); - clearMessages(); - loadRoomHistory(currentRoom); - updateRoomList(); - } - - // Display the system message - const messagesDiv = document.getElementById('messagebox'); - const messageElement = document.createElement('div'); - if (messageElement && messagesDiv) { - messagesDiv.appendChild(messageElement); - messageElement.className = 'message system-message'; - messageElement.textContent = message.content; - messagesDiv.scrollTop = messagesDiv.scrollHeight; - } - return; - } - else if (message.type == "file") { - // Only show file if it's for current room - if (message.room && message.room !== currentRoom) { - return; - } - const messagesDiv = document.getElementById('messagebox'); - let filename = message.content.replace("https://maxwellj.xyz/chookchat/uploads/", ""); - if (isImage(filename)) { - const imagePreview = document.createElement('img'); - if (imagePreview) { - if (messagesDiv) { - messagesDiv.appendChild(imagePreview); - imagePreview.src = message.content; - imagePreview.height = 300; - imagePreview.addEventListener("click", function() { - window.open(message.content, "_blank"); - }); - } - } - } else { - const fileButton = document.createElement('button'); - if (fileButton) { - if (messagesDiv) { - messagesDiv.appendChild(fileButton); - fileButton.textContent = `Open ${filename} in new tab`; - fileButton.className = "bluebutton"; - fileButton.addEventListener("click", function() { - window.open(message.content, "_blank"); - }); - messagesDiv.scrollTop = messagesDiv.scrollHeight; - } - } - } - return; - } - else if (message.type == "call") { - if (message.room && message.room !== currentRoom) { - return; - } - - const messagesDiv = document.getElementById('messagebox'); - const callButton = document.createElement('div'); - if (callButton) { - if (messagesDiv) { - messagesDiv.appendChild(callButton) - callButton.className = "message call-notification"; - callButton.textContent = `${message.username} started a Jitsi call! Click to join!`; - callButton.addEventListener('click', () => { - window.open(message.content, '_blank'); - }); - messagesDiv.scrollTop = messagesDiv.scrollHeight; - return; - } - } - } - else if (message.type == "connect") { - const messagesDiv = document.getElementById('messagebox'); - const messageElement = document.createElement('div'); - if (messageElement) { - if (messagesDiv) { - messagesDiv.appendChild(messageElement); - messageElement.className = 'message'; - messageElement.textContent = message.content; - messagesDiv.scrollTop = messagesDiv.scrollHeight; - } - } - if (document.hidden) { - const notifiction = new Notification("Chookchat", {body: messageElement.textContent}); - } - } - else if (message.type == "message") { - if (message.room && message.room !== currentRoom) { - return; - } - - const messagesDiv = document.getElementById('messagebox'); - const messageElement = document.createElement('div'); - if (messageElement) { - if (messagesDiv) { - messagesDiv.appendChild(messageElement); - messageElement.className = 'message'; - messageElement.textContent = `${message.username}: ${message.content}` ; - messagesDiv.scrollTop = messagesDiv.scrollHeight; - } - } - if (document.hidden) { - const notifiction = new Notification("Chookchat", {body: messageElement.textContent}); - } - } - else { - return - } - }; - } + ws.onmessage = handleMessage; + }; + + ws.onerror = () => { + alert("Error connecting to the server. Please check your server settings."); + resetLoginButton(); + }; + ws.onclose = () => { - alert("Chookchat has disconnected :/ Refresh the page to try again"); + if (!incorrectDetail) { + alert("Chookchat has disconnected. Refresh the page to reconnect."); + } + resetLoginButton(); + }; +} + +// Reset login button state +function resetLoginButton() { + const loginButton = document.querySelector('.btn-primary'); + if (loginButton) { + loginButton.innerHTML = 'Log in'; + loginButton.disabled = false; } } + +// Handle incoming messages +function handleMessage(event) { + if (event.data === "ping") { + ws.send("pong"); + return; + } + + const message = JSON.parse(event.data); + + // Handle error messages + if (message.type == "error") { + if (message.username == "system") { + if (message.content == "invalid-token") { + alert("Your password is incorrect! Please try again."); + location.reload(); + return; + } + if (message.content == "unknown-account") { + alert("That username isn't on the server. Maybe try registering?"); + location.reload(); + return; + } + if (message.content == "banned") { + alert("You've been banned from the server."); + location.reload(); + return; + } + } + } + + // Handle typing indicators + else if (message.type == "typing") { + handleTypingIndicator(message); + return; + } + + // Handle users list + else if (message.type == "users" && message.username == "system") { + updateOnlineUsers(message.content); + return; + } + + // Handle room users + else if (message.type == "roomUsers" && message.username == "system") { + updateRoomUsers(message); + return; + } + + // Handle rooms list + else if (message.type == "roomsList" && message.username == "system") { + try { + availableRooms = JSON.parse(message.content); + updateRoomList(); + } catch (error) { + console.error('Error parsing rooms list:', error); + } + return; + } + + // Handle room created or joined + else if (message.type == "roomCreated" || message.type == "roomJoin") { + handleRoomChange(message); + return; + } + + // Handle file messages + else if (message.type == "file") { + handleFileMessage(message); + return; + } + + // Handle call messages + else if (message.type == "call") { + handleCallMessage(message); + return; + } + + // Handle connect messages + else if (message.type == "connect") { + addSystemMessage(message.content); + + if (document.hidden) { + new Notification("Chookchat", {body: message.content}); + } + } + + // Handle regular messages + else if (message.type == "message") { + if (message.room && message.room !== currentRoom) { + // Highlight room with unread message + highlightRoom(message.room); + return; + } + + addChatMessage(message.username, message.content); + + if (document.hidden) { + new Notification("Chookchat", { + body: `${message.username}: ${message.content}` + }); + } + } +} + +// Handle typing indicator updates +function handleTypingIndicator(message) { + if (message.content == "1") { + if (username !== message.username && !typingPeople.includes(message.username)) { + typingPeople.push(message.username); + updatePeopleTyping(); + } + } else if (message.content == "0") { + if (username !== message.username && typingPeople.includes(message.username)) { + const index = typingPeople.indexOf(message.username); + typingPeople.splice(index, 1); + updatePeopleTyping(); + } + } +} + +// Update online users display +function updateOnlineUsers(usersList) { + const usersPanel = document.querySelector('.users-list'); + if (usersPanel) { + usersPanel.innerHTML = ''; + + const users = usersList.split(', '); + users.forEach(user => { + addUserToPanel(user); + }); + } +} + +// Update room users display +function updateRoomUsers(message) { + const usersInRoom = message.content; + const roomName = message.room; + + // Update current room if needed + if (roomName !== currentRoom) { + currentRoom = roomName; + updateCurrentRoomDisplay(); + } + + // Update users panel + const usersPanel = document.querySelector('.users-list'); + if (usersPanel) { + usersPanel.innerHTML = ''; + + const users = usersInRoom.split(', '); + users.forEach(user => { + addUserToPanel(user); + }); + } +} + +// Add user to the users panel +function addUserToPanel(user) { + const usersPanel = document.querySelector('.users-list'); + if (!usersPanel) return; + + const userItem = document.createElement('div'); + userItem.className = 'user-item'; + + // Create user avatar + const userAvatar = document.createElement('div'); + userAvatar.className = 'user-avatar'; + userAvatar.style.backgroundColor = getUserColor(user); + userAvatar.textContent = user.charAt(0).toUpperCase(); + + // Create user name + const userName = document.createElement('div'); + userName.className = 'user-name'; + userName.textContent = user; + + userItem.appendChild(userAvatar); + userItem.appendChild(userName); + usersPanel.appendChild(userItem); +} + +// Handle room change +function handleRoomChange(message) { + if (message.room) { + currentRoom = message.room; + updateCurrentRoomDisplay(); + clearMessages(); + loadRoomHistory(currentRoom); + updateRoomList(); + } + + // Display system message + addSystemMessage(message.content); +} + +// Handle file messages +function handleFileMessage(message) { + // Only show file if it's for current room + if (message.room && message.room !== currentRoom) { + highlightRoom(message.room); + return; + } + + const messagesDiv = document.getElementById('messagebox'); + if (!messagesDiv) return; + + const messageWrapper = document.createElement('div'); + messageWrapper.className = 'message'; + + // Create avatar + const avatar = document.createElement('div'); + avatar.className = 'message-avatar'; + avatar.style.backgroundColor = getUserColor(message.username); + avatar.textContent = message.username.charAt(0).toUpperCase(); + + // Create message content + const contentDiv = document.createElement('div'); + contentDiv.className = 'message-content'; + + // Add message header with username + const headerDiv = document.createElement('div'); + headerDiv.className = 'message-header'; + + const authorSpan = document.createElement('span'); + authorSpan.className = 'message-author'; + authorSpan.textContent = message.username; + + const timestampSpan = document.createElement('span'); + timestampSpan.className = 'message-timestamp'; + timestampSpan.textContent = new Date().toLocaleTimeString(); + + headerDiv.appendChild(authorSpan); + headerDiv.appendChild(timestampSpan); + contentDiv.appendChild(headerDiv); + + let filename = message.content.replace("https://maxwellj.xyz/chookchat/uploads/", ""); + + if (isImage(filename)) { + // Image file + const imageMessage = document.createElement('div'); + imageMessage.className = 'image-message'; + + const imagePreview = document.createElement('img'); + imagePreview.src = message.content; + imagePreview.alt = filename; + imagePreview.addEventListener("click", function() { + window.open(message.content, "_blank"); + }); + + imageMessage.appendChild(imagePreview); + contentDiv.appendChild(imageMessage); + } else { + // Other file type + const fileMessage = document.createElement('div'); + fileMessage.className = 'file-message'; + + const fileIcon = document.createElement('i'); + fileIcon.className = 'file-icon fa-solid fa-file'; + + const fileName = document.createElement('span'); + fileName.className = 'file-name'; + fileName.textContent = filename; + + const fileButton = document.createElement('button'); + fileButton.className = 'file-button'; + fileButton.textContent = 'Open'; + fileButton.addEventListener("click", function() { + window.open(message.content, "_blank"); + }); + + fileMessage.appendChild(fileIcon); + fileMessage.appendChild(fileName); + fileMessage.appendChild(fileButton); + contentDiv.appendChild(fileMessage); + } + + messageWrapper.appendChild(avatar); + messageWrapper.appendChild(contentDiv); + messagesDiv.appendChild(messageWrapper); + messagesDiv.scrollTop = messagesDiv.scrollHeight; +} + +// Handle call messages +function handleCallMessage(message) { + if (message.room && message.room !== currentRoom) { + highlightRoom(message.room); + return; + } + + const messagesDiv = document.getElementById('messagebox'); + if (!messagesDiv) return; + + const callNotification = document.createElement('div'); + callNotification.className = 'call-notification'; + callNotification.innerHTML = ` ${message.username} started a call. Click to join!`; + callNotification.addEventListener('click', () => { + window.open(message.content, '_blank'); + }); + + messagesDiv.appendChild(callNotification); + messagesDiv.scrollTop = messagesDiv.scrollHeight; +} + +// Add a system message +function addSystemMessage(content, isHistory = false) { + const messagesDiv = document.getElementById('messagebox'); + if (!messagesDiv) return; + + const messageElement = document.createElement('div'); + messageElement.className = 'system-message'; + if (isHistory) messageElement.classList.add('history-message'); + messageElement.textContent = content; + + messagesDiv.appendChild(messageElement); + messagesDiv.scrollTop = messagesDiv.scrollHeight; +} + +// Add a chat message +function addChatMessage(author, content, isHistory = false) { + const messagesDiv = document.getElementById('messagebox'); + if (!messagesDiv) return; + + const messageWrapper = document.createElement('div'); + messageWrapper.className = 'message'; + if (isHistory) messageWrapper.classList.add('history-message'); + + // Create avatar + const avatar = document.createElement('div'); + avatar.className = 'message-avatar'; + avatar.style.backgroundColor = getUserColor(author); + avatar.textContent = author.charAt(0).toUpperCase(); + + // Create message content + const contentDiv = document.createElement('div'); + contentDiv.className = 'message-content'; + + // Add message header with username + const headerDiv = document.createElement('div'); + headerDiv.className = 'message-header'; + + const authorSpan = document.createElement('span'); + authorSpan.className = 'message-author'; + authorSpan.textContent = author; + + const timestampSpan = document.createElement('span'); + timestampSpan.className = 'message-timestamp'; + timestampSpan.textContent = isHistory ? 'History' : new Date().toLocaleTimeString(); + + headerDiv.appendChild(authorSpan); + headerDiv.appendChild(timestampSpan); + contentDiv.appendChild(headerDiv); + + // Add message text + const textDiv = document.createElement('div'); + textDiv.className = 'message-text'; + textDiv.textContent = content; + contentDiv.appendChild(textDiv); + + messageWrapper.appendChild(avatar); + messageWrapper.appendChild(contentDiv); + messagesDiv.appendChild(messageWrapper); + messagesDiv.scrollTop = messagesDiv.scrollHeight; +} + +// Highlight a room with unread messages +function highlightRoom(roomName) { + const roomItems = document.querySelectorAll('.room-item'); + roomItems.forEach(item => { + if (item.dataset.room === roomName && !item.classList.contains('active')) { + item.classList.add('unread'); + } + }); +} + +// Create a new room async function createRoom(roomName) { const message = { type: 'createRoom', @@ -370,6 +650,7 @@ async function createRoom(roomName) { setTimeout(updateRoomList, 500); } +// Join a room async function joinRoom(roomName) { if (roomName === currentRoom) { const usersMessage = { @@ -392,10 +673,14 @@ async function joinRoom(roomName) { }; ws.send(JSON.stringify(message)); } + +// Send a message function sendMessage() { const messageInput = document.getElementById('messageInput'); const message = messageInput.value.trim(); + if (!message) return; + const processedMessage = { "type": "message", "username": username, @@ -404,7 +689,7 @@ function sendMessage() { "content": message } - if (processedMessage.content && ws && ws.readyState === WebSocket.OPEN) { + if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(processedMessage)); messageInput.value = ''; @@ -424,19 +709,91 @@ function sendMessage() { } } +// Show file upload modal function showFileUpload() { - const fileUploadElement = document.getElementById("upload"); - if (fileUploadElement) { - fileUploadElement.style.display = "block"; + const uploadModal = document.getElementById("upload-modal"); + if (uploadModal) { + uploadModal.classList.remove("hidden"); } } +// Hide file upload modal +function hideFileUpload() { + const uploadModal = document.getElementById("upload-modal"); + if (uploadModal) { + uploadModal.classList.add("hidden"); + } + + // Clear file input + const fileInput = document.getElementById("fileupload"); + if (fileInput) { + fileInput.value = ""; + } + + // Clear preview + const previewDiv = document.getElementById("upload-preview"); + if (previewDiv) { + previewDiv.innerHTML = ""; + } +} + +// Preview file before upload +function previewFile() { + const fileInput = document.getElementById("fileupload"); + const previewDiv = document.getElementById("upload-preview"); + + if (!fileInput || !previewDiv) return; + + previewDiv.innerHTML = ""; + + if (!fileInput.files.length) return; + + const file = fileInput.files[0]; + const fileName = file.name; + + if (file.type.startsWith('image/')) { + const img = document.createElement("img"); + img.file = file; + previewDiv.appendChild(img); + + const reader = new FileReader(); + reader.onload = (e) => { img.src = e.target.result; }; + reader.readAsDataURL(file); + } else { + const fileInfo = document.createElement("div"); + fileInfo.innerHTML = ` ${fileName} (${formatFileSize(file.size)})`; + previewDiv.appendChild(fileInfo); + } +} + +// Format file size +function formatFileSize(bytes) { + if (bytes < 1024) return bytes + ' bytes'; + else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; + else return (bytes / 1048576).toFixed(1) + ' MB'; +} + +// Upload a file async function uploadFile() { const fileInput = document.getElementById("fileupload"); if (!fileInput.files.length) { - alert("Please add a file!"); + alert("Please select a file to upload"); return; } + + // Check file size + if (fileInput.files[0].size > 10485760) { + alert("File is too large. Maximum size is 10MB."); + return; + } + + // Update button state + const uploadButton = document.querySelector('#upload-modal .btn-primary'); + if (uploadButton) { + uploadButton.innerHTML = ' Uploading...'; + uploadButton.disabled = true; + } + const formData = new FormData(); formData.append("file", fileInput.files[0]); formData.append("room", currentRoom); @@ -447,8 +804,10 @@ async function uploadFile() { mode: "no-cors", body: formData }); + if (response.ok) { - const result = await response.text(); + hideFileUpload(); + const processedMessage = { "type": "message", "username": username, @@ -456,58 +815,24 @@ async function uploadFile() { "room": currentRoom, "content": `Sent a file` } - if (processedMessage && ws && ws.readyState === WebSocket.OPEN) { + if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(processedMessage)); } } else { - alert("Something went wrong lmao"); + alert("Failed to upload file. Please try again."); } } catch (error) { - alert(error); + alert("Error uploading file: " + error.message); } - const fileUploadElement = document.getElementById("upload"); - if (fileUploadElement) { - fileUploadElement.style.display = "none"; + + // Reset button state + if (uploadButton) { + uploadButton.innerHTML = 'Upload'; + uploadButton.disabled = false; } } -document.getElementById('messageInput').addEventListener('keypress', (event) => { - if (event.key === 'Enter') { - sendMessage(); - } -}); - -document.getElementById('password').addEventListener('keypress', (event) => { - if (event.key === 'Enter') { - connect(); - } -}); - -async function doRegister(username, password) { - return fetch(`${getSignupUrl()}username:{${username}}token:{${md5(password)}}`).then((response)=>response.json()).then((responseJson)=>{return responseJson}); -} - -async function register() { - username = document.getElementById('username').value; - password = document.getElementById('password').value; - - if (!username || !password) { - alert('Please enter a username and password'); - return; - } - - const response = await this.doRegister(username, password); - if (response.type == "success") { - alert("Account created! Click 'log in' to access Chookchat!") - } else { - alert(`We couldn't create your account :( Reason: ${response.content}`) - } -} - -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - +// Start typing indicator function startTypingIndicator() { if (typingTimeout) { clearTimeout(typingTimeout); @@ -540,36 +865,37 @@ function startTypingIndicator() { }, 5000); } +// Update typing indicator display function updatePeopleTyping() { const typingDiv = document.getElementById('typing'); if (typingDiv) { if (typingPeople.length === 0) { typingDiv.textContent = ''; } else if (typingPeople.length === 1) { - typingDiv.textContent = `${typingPeople[0]} is typing...`; + typingDiv.innerHTML = ` ${typingPeople[0]} is typing...`; } else if (typingPeople.length === 2) { - typingDiv.textContent = `${typingPeople[0]} and ${typingPeople[1]} are typing...`; + typingDiv.innerHTML = ` ${typingPeople[0]} and ${typingPeople[1]} are typing...`; } else { - typingDiv.textContent = `${typingPeople.length} people are typing...`; + typingDiv.innerHTML = ` ${typingPeople.length} people are typing...`; } } } -const characters ='abcdefghijklmnopqrstuvwxyz'; - -function generateString(length) { +// Generate random string for Jitsi meeting +function generateRandomString(length) { + const characters ='abcdefghijklmnopqrstuvwxyz'; let result = ''; const charactersLength = characters.length; - for ( let i = 0; i < length; i++ ) { + for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } +// Start a Jitsi meeting function startMeeting() { - const link = `https://meet.jit.si/chookchat-${generateString(15)}`; - alert("Note: You may need to sign in to Jitsi to start the meeting. We'll take you there in a moment...") - window.open(link, '_blank'); + const link = `https://meet.jit.si/chookchat-${generateRandomString(15)}`; + const processedMessage = { "type": "call", "username": username, @@ -577,24 +903,19 @@ function startMeeting() { "room": currentRoom, "content": link }; + if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(processedMessage)); + window.open(link, '_blank'); } else { - alert("Something went wrong. Refreshing might do the trick :)") + alert("Something went wrong. Refresh the page and try again."); } } -document.getElementById('messageInput').addEventListener('input', startTypingIndicator); - +// Update the room list UI async function updateRoomList() { - let roomListDiv = document.getElementById('room-list'); - if (!roomListDiv) { - const messagingDiv = document.querySelector('.messaging-container'); - roomListDiv = document.createElement('div'); - roomListDiv.id = 'room-list'; - roomListDiv.className = 'room-list'; - messagingDiv.insertBefore(roomListDiv, messagingDiv.firstChild); - } + const roomListDiv = document.getElementById('room-list'); + if (!roomListDiv) return; try { availableRooms = await getRooms(); @@ -602,39 +923,45 @@ async function updateRoomList() { console.error('Error updating room list:', error); } - roomListDiv.innerHTML = '
Rooms
'; - - availableRooms.forEach(room => { - const roomElement = document.createElement('div'); - roomElement.className = `room-item ${room === currentRoom ? 'active' : ''}`; - roomElement.textContent = room; - roomElement.addEventListener('click', () => joinRoom(room)); - roomListDiv.appendChild(roomElement); - }); - - const createRoomButton = document.createElement('button'); - createRoomButton.className = 'create-room-button'; - createRoomButton.textContent = '+ New Room'; - createRoomButton.addEventListener('click', promptCreateRoom); - roomListDiv.appendChild(createRoomButton); + // Clear existing rooms except category header + const roomCategory = roomListDiv.querySelector('.room-category'); + if (roomCategory) { + const header = roomCategory.querySelector('.room-category-header'); + roomCategory.innerHTML = ''; + if (header) roomCategory.appendChild(header); + + // Add room items + availableRooms.forEach(room => { + const roomElement = document.createElement('div'); + roomElement.className = `room-item ${room === currentRoom ? 'active' : ''}`; + roomElement.dataset.room = room; + roomElement.innerHTML = ` ${room}`; + roomElement.addEventListener('click', () => joinRoom(room)); + roomCategory.appendChild(roomElement); + }); + } } +// Update current room display function updateCurrentRoomDisplay() { - const roomTitle = document.getElementById('room-title'); - if (roomTitle) { - roomTitle.remove(); + const roomNameElement = document.getElementById('current-room-name'); + if (roomNameElement) { + roomNameElement.textContent = currentRoom; } + // Update active state in room list const roomItems = document.querySelectorAll('.room-item'); roomItems.forEach(item => { - if (item.textContent === currentRoom) { + if (item.dataset.room === currentRoom) { item.classList.add('active'); + item.classList.remove('unread'); } else { item.classList.remove('active'); } }); } +// Clear message display function clearMessages() { const messagebox = document.getElementById('messagebox'); if (messagebox) { @@ -642,6 +969,7 @@ function clearMessages() { } } +// Load room message history async function loadRoomHistory(roomName) { try { const history = await getRoomHistory(roomName); @@ -651,10 +979,21 @@ async function loadRoomHistory(roomName) { lines.forEach(line => { if (line.trim()) { - const messageElement = document.createElement('div'); - messageElement.className = 'message history-message'; - messageElement.textContent = line; - messagebox.appendChild(messageElement); + // Parse history line to extract username and message + const historyMatch = line.match(/^(.*?):\s(.*)$/); + + if (historyMatch && historyMatch.length >= 3) { + // If it's a regular message format (username: message) + const historyUsername = historyMatch[1].trim(); + const historyContent = historyMatch[2].trim(); + addChatMessage(historyUsername, historyContent, true); + } else if (line.includes("joined") || line.includes("left") || line.includes("created")) { + // System messages + addSystemMessage(line, true); + } else { + // Fallback for any other format + addSystemMessage(line, true); + } } }); @@ -665,6 +1004,7 @@ async function loadRoomHistory(roomName) { } } +// Prompt to create a new room function promptCreateRoom() { const roomName = prompt('Enter a name for the new room:'); if (roomName && roomName.trim()) { @@ -672,30 +1012,135 @@ function promptCreateRoom() { } } +// Show eggs panel function showEggs() { - const eggsPanel = document.getElementById('eggs'); - eggsPanel.style.display = "block"; - const mainBox = document.querySelector('#messaging .box'); - if (eggsPanel.classList.contains('visible')) { - mainBox.style.width = 'calc(100% - 310px)'; - } else { - mainBox.style.width = '100%'; + const eggsPanel = document.getElementById('eggs-panel'); + if (eggsPanel) { + eggsPanel.classList.remove('hidden'); + eggsPanel.classList.add('visible'); } } +// Close eggs panel function closeEggs() { - const eggsPanel = document.getElementById('eggs'); - eggsPanel.style.display = "block"; -} -const uploadField = document.getElementById("fileupload"); - -uploadField.onchange = function() { - if(this.files[0].size > 10485760) { - alert("That file is too big bro. Not as big as my (message terminated)"); - this.value = ""; + const eggsPanel = document.getElementById('eggs-panel'); + if (eggsPanel) { + eggsPanel.classList.remove('visible'); + setTimeout(() => { + eggsPanel.classList.add('hidden'); + }, 200); } -}; +} -// Eggs begin here +// Get or generate consistent user color +function getUserColor(username) { + if (!avatarColors[username]) { + // Generate a color based on username + const hue = Math.abs(hashString(username) % 360); + avatarColors[username] = `hsl(${hue}, 70%, 60%)`; + } + return avatarColors[username]; +} +// Simple string hash function +function hashString(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) - hash) + str.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + return hash; +} +// Register a new account +async function doRegister(username, password) { + return fetch(`${getSignupUrl()}username:{${username}}token:{${md5(password)}}`) + .then(response => response.json()) + .then(responseJson => responseJson); +} + +// Register handler +async function register() { + username = document.getElementById('username').value; + password = document.getElementById('password').value; + + if (!username || !password) { + alert('Please enter a username and password'); + return; + } + + // Update button state + const registerButton = document.querySelector('.btn-success'); + if (registerButton) { + registerButton.innerHTML = ' Registering...'; + registerButton.disabled = true; + } + + try { + const response = await doRegister(username, password); + if (response.type == "success") { + alert("Account created! Click 'Log in' to access Chookchat!"); + } else { + alert(`We couldn't create your account: ${response.content}`); + } + } catch (error) { + alert("Error registering account: " + error.message); + } + + // Reset button state + if (registerButton) { + registerButton.innerHTML = 'Register'; + registerButton.disabled = false; + } +} + +// Notepad egg +function eggNotepad() { + const eggContent = document.getElementById('egg-content'); + if (eggContent) { + eggContent.innerHTML = ` +
+ +
+ + +
+
+ `; + + // Load saved content if available + const savedNote = localStorage.getItem('chookchat-notepad'); + if (savedNote) { + eggContent.querySelector('.notepad-textarea').value = savedNote; + } + } +} + +// Save notepad content +function saveNotepad() { + const textarea = document.querySelector('.notepad-textarea'); + if (textarea) { + localStorage.setItem('chookchat-notepad', textarea.value); + alert('Notes saved!'); + } +} + +// Clear notepad content +function clearNotepad() { + const textarea = document.querySelector('.notepad-textarea'); + if (textarea && confirm('Are you sure you want to clear your notes?')) { + textarea.value = ''; + localStorage.removeItem('chookchat-notepad'); + } +} + +// File upload size validation +const uploadField = document.getElementById("fileupload"); +if (uploadField) { + uploadField.onchange = function() { + if (this.files[0] && this.files[0].size > 10485760) { + alert("File is too large. Maximum size is 10MB."); + this.value = ""; + } + }; +} \ No newline at end of file