2024-11-23 18:10:56 +11:00
alert ( "Chookchat is currently in early development, expect bugs! Please don't try breaking the public server, do that with your own test server (read more in the Git repo). Thanks for trying Chookchat!" )
2024-11-05 20:35:10 +11:00
let ws ;
let username ;
let password ;
2024-11-24 16:07:45 +11:00
let typingTimeout ;
2025-06-06 10:03:27 +10:00
let typingPeople = [ ] ;
2025-03-14 08:44:06 +11:00
let currentRoom = "general" ;
let availableRooms = [ "general" ] ;
2025-06-06 10:03:27 +10:00
let isMobile = window . innerWidth < 768 ;
let avatarColors = { } ;
2024-11-05 20:35:10 +11:00
2025-06-06 10:03:27 +10:00
// 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 = '<i class="fa-solid fa-bars"></i>' ;
menuToggle . addEventListener ( 'click' , toggleSidebar ) ;
const chatHeader = document . querySelector ( '.chat-header' ) ;
if ( chatHeader ) {
chatHeader . insertBefore ( menuToggle , chatHeader . firstChild ) ;
}
2024-11-23 18:10:56 +11:00
}
2025-06-06 17:32:48 +10:00
document . getElementById ( 'messageInput' ) . addEventListener ( 'input' , function ( ) {
this . style . height = 'auto' ;
this . style . height = ( this . scrollHeight ) + 'px' ;
} ) ;
2025-06-06 10:03:27 +10:00
// Add event listeners
2025-06-06 17:32:48 +10:00
document . getElementById ( 'messageInput' ) . addEventListener ( 'keydown' , function ( event ) {
if ( event . key === 'Enter' && ! event . shiftKey ) {
event . preventDefault ( ) ;
2025-06-06 10:03:27 +10:00
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' ) ;
}
}
} ) ;
} ) ;
2024-11-23 18:10:56 +11:00
2025-06-06 10:03:27 +10:00
// Toggle sidebar on mobile
function toggleSidebar ( ) {
const sidebar = document . querySelector ( '.sidebar' ) ;
if ( sidebar ) {
sidebar . classList . toggle ( 'visible' ) ;
}
}
2024-11-23 18:10:56 +11:00
2025-06-06 10:03:27 +10:00
// Show server config
2024-11-05 20:35:10 +11:00
function showConfig ( ) {
2025-06-06 10:03:27 +10:00
const serverconfig = document . getElementById ( 'serverconfig' ) ;
2024-11-05 20:35:10 +11:00
if ( serverconfig ) {
2025-06-06 10:03:27 +10:00
serverconfig . style . display = serverconfig . style . display === 'block' ? 'none' : 'block' ;
2024-11-05 20:35:10 +11:00
}
}
2025-06-06 10:03:27 +10:00
// Hash password using MD5
2024-11-05 20:35:10 +11:00
function md5 ( string ) {
return CryptoJS . MD5 ( string ) . toString ( ) ;
}
2025-06-06 10:03:27 +10:00
// Get WebSocket URL
2024-11-05 20:35:10 +11:00
function getUrl ( ) {
const serverUrl = document . getElementById ( 'serverUrl' ) . value . trim ( ) ;
const serverPort = document . getElementById ( 'serverPort' ) . value ;
const useWss = document . getElementById ( 'securityStatus' ) . checked ;
const protocol = useWss ? 'wss' : 'ws' ;
const cleanUrl = serverUrl . replace ( /^(https?:\/\/|wss?:\/\/)/ , '' ) ;
return ` ${ protocol } :// ${ cleanUrl } : ${ serverPort } /api/websocket ` ;
}
2025-06-06 10:03:27 +10:00
// Get signup URL
2024-11-05 20:35:10 +11:00
function getSignupUrl ( ) {
const serverUrl = document . getElementById ( 'serverUrl' ) . value . trim ( ) ;
const serverPort = document . getElementById ( 'serverPort' ) . value ;
const useWss = document . getElementById ( 'securityStatus' ) . checked ;
const protocol = useWss ? 'https' : 'http' ;
const cleanUrl = serverUrl . replace ( /^(https?:\/\/|wss?:\/\/)/ , '' ) ;
return ` ${ protocol } :// ${ cleanUrl } : ${ serverPort } /api/createaccount/ ` ;
}
2025-06-06 10:03:27 +10:00
// Get upload URL
2024-11-25 14:25:42 +11:00
function getUploadUrl ( ) {
const serverUrl = document . getElementById ( 'serverUrl' ) . value . trim ( ) ;
const serverPort = document . getElementById ( 'serverPort' ) . value ;
const useWss = document . getElementById ( 'securityStatus' ) . checked ;
const protocol = useWss ? 'https' : 'http' ;
2024-11-05 20:35:10 +11:00
2024-11-25 14:25:42 +11:00
const cleanUrl = serverUrl . replace ( /^(https?:\/\/|wss?:\/\/)/ , '' ) ;
return ` ${ protocol } :// ${ cleanUrl } : ${ serverPort } /api/upload ` ;
}
2025-06-06 10:03:27 +10:00
// Get available rooms
2025-03-14 08:44:06 +11:00
async function getRooms ( ) {
try {
const serverUrl = document . getElementById ( 'serverUrl' ) . value . trim ( ) ;
const serverPort = document . getElementById ( 'serverPort' ) . value ;
const useWss = document . getElementById ( 'securityStatus' ) . checked ;
const protocol = useWss ? 'https' : 'http' ;
const cleanUrl = serverUrl . replace ( /^(https?:\/\/|wss?:\/\/)/ , '' ) ;
const url = ` ${ protocol } :// ${ cleanUrl } : ${ serverPort } /api/rooms ` ;
const response = await fetch ( url , {
mode : "no-cors"
2025-06-06 10:03:27 +10:00
} ) ;
2025-03-14 08:44:06 +11:00
const data = await response . json ( ) ;
return JSON . parse ( data . content ) ;
} catch ( error ) {
console . error ( 'Error fetching rooms:' , error ) ;
return [ "general" ] ;
}
}
2025-06-06 10:03:27 +10:00
// Get room message history
2025-03-14 08:44:06 +11:00
async function getRoomHistory ( roomName ) {
try {
const serverUrl = document . getElementById ( 'serverUrl' ) . value . trim ( ) ;
const serverPort = document . getElementById ( 'serverPort' ) . value ;
const useWss = document . getElementById ( 'securityStatus' ) . checked ;
const protocol = useWss ? 'https' : 'http' ;
const cleanUrl = serverUrl . replace ( /^(https?:\/\/|wss?:\/\/)/ , '' ) ;
const url = ` ${ protocol } :// ${ cleanUrl } : ${ serverPort } /api/room/ ${ roomName } /history ` ;
const response = await fetch ( url , {
mode : "no-cors"
2025-06-06 10:03:27 +10:00
} ) ;
2025-03-14 08:44:06 +11:00
const history = await response . text ( ) ;
return history ;
} catch ( error ) {
console . error ( 'Error fetching room history:' , error ) ;
return '' ;
}
}
2025-06-06 10:03:27 +10:00
// Image mime types and extensions
2025-03-14 08:44:06 +11:00
const imageMimeTypes = [
2025-06-06 10:03:27 +10:00
'image/webp' ,
'image/tiff' ,
'image/svg+xml' ,
'image/png' ,
'image/jpeg' ,
'image/vnd.microsoft.icon' ,
'image/gif' ,
'image/bmp' ,
2025-03-14 08:44:06 +11:00
] ;
const imageTypes = [
'png' ,
'jpg' ,
'jpeg' ,
'svg' ,
'tiff' ,
'gif' ,
'webp' ,
'bmp'
] ;
2025-06-06 10:03:27 +10:00
// Check if file is an image
2025-03-14 08:44:06 +11:00
function isImage ( file ) {
const fileSplit = file . split ( "." ) ;
2025-06-06 10:03:27 +10:00
return imageTypes . includes ( fileSplit [ fileSplit . length - 1 ] . toLowerCase ( ) ) ;
2025-03-14 08:44:06 +11:00
}
2025-06-06 10:03:27 +10:00
// Connect to the server
2025-03-14 08:44:06 +11:00
async function connect ( ) {
2024-11-05 20:35:10 +11:00
username = document . getElementById ( 'username' ) . value ;
password = document . getElementById ( 'password' ) . value ;
if ( ! username || ! password ) {
alert ( 'Please enter a username and password' ) ;
return ;
}
const wsUrl = getUrl ( ) ;
if ( ws ) {
ws . close ( ) ;
}
2025-06-06 10:03:27 +10:00
// Show connecting state
const loginButton = document . querySelector ( '.btn-primary' ) ;
if ( loginButton ) {
loginButton . innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Connecting...' ;
loginButton . disabled = true ;
}
2024-11-05 20:35:10 +11:00
2025-06-06 10:03:27 +10:00
ws = new WebSocket ( wsUrl ) ;
2024-11-23 18:10:56 +11:00
var incorrectDetail = 0 ;
2025-03-14 08:44:06 +11:00
ws . onopen = async ( ) => {
2024-11-24 14:13:05 +11:00
if ( typeof Notification !== "undefined" ) {
Notification . requestPermission ( ) ;
}
2024-11-05 20:35:10 +11:00
console . log ( 'Connected!' ) ;
2025-06-06 10:03:27 +10:00
document . getElementById ( 'login-container' ) . style . display = 'none' ;
document . getElementById ( 'app' ) . classList . remove ( 'hidden' ) ;
// Set current username in UI
document . getElementById ( 'current-username' ) . textContent = username ;
2025-03-14 08:44:06 +11:00
try {
availableRooms = await getRooms ( ) ;
updateRoomList ( ) ;
} catch ( error ) {
console . error ( 'Failed to get room list:' , error ) ;
availableRooms = [ "general" ] ;
}
2024-11-23 18:10:56 +11:00
const connectMessage = {
"type" : "connect" ,
"username" : username ,
"token" : md5 ( password ) ,
"content" : ` ${ username } joined the room! `
}
ws . send ( JSON . stringify ( connectMessage ) ) ;
2025-03-14 08:44:06 +11:00
joinRoom ( "general" ) ;
2025-06-06 10:03:27 +10:00
ws . onmessage = handleMessage ;
} ;
ws . onerror = ( ) => {
alert ( "Error connecting to the server. Please check your server settings." ) ;
resetLoginButton ( ) ;
} ;
ws . onclose = ( ) => {
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 ;
}
}
2025-06-06 17:32:48 +10:00
function markdownToHtmlDiv ( markdown ) {
// Emoji mapping
const emojiMap = {
// 😀 Faces
smile : '😄' ,
happy : '😊' ,
grin : '😁' ,
laugh : '😂' ,
joy : '🤣' ,
wink : '😉' ,
blush : '☺️' ,
cool : '😎' ,
smirk : '😏' ,
thinking : '🤔' ,
neutral : '😐' ,
expressionless : '😑' ,
sleepy : '😴' ,
dizzy : '😵' ,
surprised : '😲' ,
scream : '😱' ,
cry : '😢' ,
sob : '😭' ,
angry : '😠' ,
rage : '😡' ,
yum : '😋' ,
relieved : '😌' ,
confused : '😕' ,
nerd : '🤓' ,
zany : '🤪' ,
shush : '🤫' ,
hush : '🤐' ,
// ❤️ Emotions
heart : '❤️' ,
heartbroken : '💔' ,
love : '😍' ,
kiss : '😘' ,
hug : '🤗' ,
clap : '👏' ,
pray : '🙏' ,
ok : '👌' ,
fingerscrossed : '🤞' ,
thumbsup : '👍' ,
thumbsdown : '👎' ,
eyes : '👀' ,
// 🔥 Reactions
fire : '🔥' ,
100 : '💯' ,
poop : '💩' ,
poo : '💩' ,
skull : '💀' ,
explosion : '💥' ,
mindblown : '🤯' ,
party : '🥳' ,
tada : '🎉' ,
balloon : '🎈' ,
// 🐶 Animals
chicken : '🐔' ,
dog : '🐶' ,
cat : '🐱' ,
fox : '🦊' ,
panda : '🐼' ,
pig : '🐷' ,
cow : '🐮' ,
rabbit : '🐰' ,
bear : '🐻' ,
unicorn : '🦄' ,
monkey : '🐒' ,
dragon : '🐉' ,
snake : '🐍' ,
// 🍔 Food & Drink
pizza : '🍕' ,
burger : '🍔' ,
fries : '🍟' ,
hotdog : '🌭' ,
taco : '🌮' ,
ramen : '🍜' ,
sushi : '🍣' ,
icecream : '🍨' ,
cake : '🎂' ,
coffee : '☕' ,
tea : '🍵' ,
beer : '🍺' ,
// 🌍 Nature & Weather
sun : '☀️' ,
moon : '🌙' ,
star : '⭐' ,
cloud : '☁️' ,
rain : '🌧️' ,
snow : '❄️' ,
lightning : '⚡' ,
rainbow : '🌈' ,
tree : '🌳' ,
flower : '🌸' ,
// ⚽ Activities
soccer : '⚽' ,
basketball : '🏀' ,
football : '🏈' ,
baseball : '⚾' ,
tennis : '🎾' ,
bowling : '🎳' ,
video _game : '🎮' ,
chess : '♟️' ,
music : '🎵' ,
guitar : '🎸' ,
mic : '🎤' ,
// 💡 Objects & Symbols
lightbulb : '💡' ,
phone : '📱' ,
laptop : '💻' ,
bomb : '💣' ,
money : '💰' ,
star2 : '🌟' ,
warning : '⚠️' ,
check : '✅' ,
x : '❌' ,
question : '❓' ,
exclamation : '❗' ,
infinity : '♾️' ,
hourglass : '⏳' ,
clock : '🕒' ,
pepe : '🐸' ,
troll : '😈' ,
sus : '🧐' ,
amongus : '🟥' ,
monke : '🐵' ,
chad : '🦍' ,
gigachad : '💪' ,
wojak : '😔' ,
feelsbadman : '😞' ,
feelsgoodman : '😌' ,
yikes : '😬' ,
clown : '🤡' ,
clownworld : '🌎🤡' ,
triggered : '😡' ,
pog : '😲' ,
kek : '🤣' ,
based : '😎' ,
cringe : '😖' ,
dab : '🕺' ,
sigma : '🧠' ,
npc : '🤖' ,
doomer : '🌑' ,
zoomer : '⚡' ,
boomer : '👴' ,
sheesh : '😤' ,
rickroll : '🕺🎶' ,
trolled : '🧌' ,
rekt : '💀' ,
skillissue : '📉' ,
bro : '🙄' ,
cope : '😢' ,
ratio : '➗' ,
amogus : '👁️👄👁️' ,
} ;
// Escape HTML first
let html = markdown
. replace ( /&/g , '&' )
. replace ( /</g , '<' )
. replace ( />/g , '>' ) ;
// Code blocks (```...```)
html = html . replace ( / ` ` ` ( [ ^ ` ` ` ] * ) ` ` ` / g s , ( m a t c h , p 1 ) = >
` <pre><code> ${ p1 . trim ( ) } </code></pre> `
) ;
// Inline code (`code`)
html = html . replace ( /`([^`\n]+)`/g , ( match , p1 ) => ` <code> ${ p1 } </code> ` ) ;
// Emojis :emoji_name:
html = html . replace ( /:([a-z0-9_]+):/gi , ( match , p1 ) => emojiMap [ p1 ] || match ) ;
// Process overlapping formatting in order: Bold+Underline+Italic
html = parseFormatting ( html ) ;
// Replace line breaks
html = html . replace ( /\n/g , '<br>' ) ;
return html ;
}
// Parse overlapping formatting using tokens
function parseFormatting ( text ) {
// Define replacement tokens and their HTML
const formattingRules = [
{ regex : /\*\*\*(.*?)\*\*\*/g , html : '<strong><em>$1</em></strong>' } , // ***bold italic***
{ regex : /___(.*?)___/g , html : '<u><em>$1</em></u>' } , // ___underline italic___
{ regex : /\*\*(.*?)\*\*/g , html : '<strong>$1</strong>' } , // **bold**
{ regex : /__(.*?)__/g , html : '<u>$1</u>' } , // __underline__
{ regex : /\*(.*?)\*/g , html : '<em>$1</em>' } , // *italic*
{ regex : /_(.*?)_/g , html : '<em>$1</em>' } , // _italic_
{ regex : /~~(.*?)~~/g , html : ' < s > $1 < /s>' } / / ~ ~ strike ~ ~
] ;
// Apply formatting in order
for ( const rule of formattingRules ) {
text = text . replace ( rule . regex , rule . html ) ;
}
return text ;
}
2025-06-06 10:03:27 +10:00
// 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 ( ) ;
2025-03-14 08:44:06 +11:00
return ;
}
2025-06-06 10:03:27 +10:00
if ( message . content == "unknown-account" ) {
alert ( "That username isn't on the server. Maybe try registering?" ) ;
location . reload ( ) ;
2025-03-14 08:44:06 +11:00
return ;
}
2025-06-06 10:03:27 +10:00
if ( message . content == "banned" ) {
alert ( "You've been banned from the server." ) ;
location . reload ( ) ;
2024-11-25 14:25:42 +11:00
return ;
}
2025-06-06 10:03:27 +10:00
}
2024-11-05 20:35:10 +11:00
}
2025-06-06 10:03:27 +10:00
// 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 ( ) ;
2024-11-23 18:10:56 +11:00
}
2025-06-06 10:03:27 +10:00
// Display system message
addSystemMessage ( message . content ) ;
2024-11-05 20:35:10 +11:00
}
2025-06-06 10:03:27 +10:00
// Handle file messages
2025-06-07 10:06:46 +10:00
function handleFileMessage ( message , isHistory = false ) {
2025-06-06 10:03:27 +10:00
// 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' ;
2025-06-07 10:06:46 +10:00
if ( isHistory ) messageWrapper . classList . add ( 'history-message' ) ;
2025-06-06 10:03:27 +10:00
// 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' ;
2025-06-07 10:06:46 +10:00
timestampSpan . textContent = isHistory ? 'History' : new Date ( ) . toLocaleTimeString ( ) ;
2025-06-06 10:03:27 +10:00
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 = ` <i class="fa-solid fa-phone"></i> ${ 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' ;
2025-06-06 17:32:48 +10:00
textDiv . innerHTML = markdownToHtmlDiv ( content ) ;
2025-06-06 10:03:27 +10:00
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
2025-03-14 08:44:06 +11:00
async function createRoom ( roomName ) {
const message = {
type : 'createRoom' ,
username : username ,
token : md5 ( password ) ,
room : roomName ,
content : ""
} ;
ws . send ( JSON . stringify ( message ) ) ;
setTimeout ( updateRoomList , 500 ) ;
}
2024-11-05 20:35:10 +11:00
2025-06-06 10:03:27 +10:00
// Join a room
2025-03-14 08:44:06 +11:00
async function joinRoom ( roomName ) {
if ( roomName === currentRoom ) {
const usersMessage = {
type : 'getUsersInRoom' ,
username : username ,
token : md5 ( password ) ,
room : roomName ,
content : ""
} ;
ws . send ( JSON . stringify ( usersMessage ) ) ;
return ;
}
const message = {
type : 'joinRoom' ,
username : username ,
token : md5 ( password ) ,
room : roomName ,
content : ""
} ;
ws . send ( JSON . stringify ( message ) ) ;
}
2025-06-06 10:03:27 +10:00
// Send a message
2024-11-05 20:35:10 +11:00
function sendMessage ( ) {
const messageInput = document . getElementById ( 'messageInput' ) ;
const message = messageInput . value . trim ( ) ;
2025-06-06 10:03:27 +10:00
if ( ! message ) return ;
2024-11-23 18:10:56 +11:00
const processedMessage = {
"type" : "message" ,
"username" : username ,
"token" : md5 ( password ) ,
2025-03-14 08:44:06 +11:00
"room" : currentRoom ,
2024-11-23 18:10:56 +11:00
"content" : message
2025-03-14 08:44:06 +11:00
}
2024-11-23 18:10:56 +11:00
2025-06-06 10:03:27 +10:00
if ( ws && ws . readyState === WebSocket . OPEN ) {
2024-11-23 18:10:56 +11:00
ws . send ( JSON . stringify ( processedMessage ) ) ;
2024-11-05 20:35:10 +11:00
messageInput . value = '' ;
2024-11-24 16:07:45 +11:00
if ( typingTimeout ) {
clearTimeout ( typingTimeout ) ;
}
const stoppedTypingMessage = {
"type" : "typing" ,
"username" : username ,
"token" : md5 ( password ) ,
2025-03-14 08:44:06 +11:00
"room" : currentRoom ,
2024-11-24 16:07:45 +11:00
"content" : "0"
} ;
if ( ws && ws . readyState === WebSocket . OPEN ) {
ws . send ( JSON . stringify ( stoppedTypingMessage ) ) ;
}
}
2024-11-05 20:35:10 +11:00
}
2025-06-06 10:03:27 +10:00
// Show file upload modal
2024-11-25 14:25:42 +11:00
function showFileUpload ( ) {
2025-06-06 10:03:27 +10:00
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 = ` <i class="fa-solid fa-file"></i> ${ fileName } ( ${ formatFileSize ( file . size ) } ) ` ;
previewDiv . appendChild ( fileInfo ) ;
2024-11-25 14:25:42 +11:00
}
}
2025-06-06 10:03:27 +10:00
// 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
2024-11-25 14:25:42 +11:00
async function uploadFile ( ) {
const fileInput = document . getElementById ( "fileupload" ) ;
if ( ! fileInput . files . length ) {
2025-06-06 10:03:27 +10:00
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." ) ;
2024-11-25 14:25:42 +11:00
return ;
}
2025-06-06 10:03:27 +10:00
// Update button state
const uploadButton = document . querySelector ( '#upload-modal .btn-primary' ) ;
if ( uploadButton ) {
uploadButton . innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Uploading...' ;
uploadButton . disabled = true ;
}
2024-11-25 14:25:42 +11:00
const formData = new FormData ( ) ;
formData . append ( "file" , fileInput . files [ 0 ] ) ;
2025-03-14 08:44:06 +11:00
formData . append ( "room" , currentRoom ) ;
2025-06-06 17:32:48 +10:00
formData . append ( "username" , username ) ;
formData . append ( "token" , md5 ( password ) ) ;
2025-03-14 08:44:06 +11:00
2024-11-25 14:25:42 +11:00
try {
const response = await fetch ( getUploadUrl ( ) , {
method : 'POST' ,
mode : "no-cors" ,
body : formData
} ) ;
2025-06-06 10:03:27 +10:00
2024-11-25 14:25:42 +11:00
if ( response . ok ) {
2025-06-06 10:03:27 +10:00
hideFileUpload ( ) ;
2024-11-25 14:25:42 +11:00
} else {
2025-06-06 10:03:27 +10:00
alert ( "Failed to upload file. Please try again." ) ;
2024-11-25 14:25:42 +11:00
}
} catch ( error ) {
2025-06-06 10:03:27 +10:00
alert ( "Error uploading file: " + error . message ) ;
2024-11-25 14:25:42 +11:00
}
2025-06-06 10:03:27 +10:00
// Reset button state
if ( uploadButton ) {
uploadButton . innerHTML = 'Upload' ;
uploadButton . disabled = false ;
2025-03-14 08:44:06 +11:00
}
2024-11-05 20:35:10 +11:00
}
2024-11-24 16:07:45 +11:00
2025-06-06 10:03:27 +10:00
// Start typing indicator
2024-11-24 16:07:45 +11:00
function startTypingIndicator ( ) {
if ( typingTimeout ) {
clearTimeout ( typingTimeout ) ;
}
const typingMessage = {
"type" : "typing" ,
"username" : username ,
"token" : md5 ( password ) ,
2025-03-14 08:44:06 +11:00
"room" : currentRoom ,
2024-11-24 16:07:45 +11:00
"content" : "1"
} ;
if ( ws && ws . readyState === WebSocket . OPEN ) {
ws . send ( JSON . stringify ( typingMessage ) ) ;
}
typingTimeout = setTimeout ( ( ) => {
const stoppedTypingMessage = {
"type" : "typing" ,
"username" : username ,
"token" : md5 ( password ) ,
2025-03-14 08:44:06 +11:00
"room" : currentRoom ,
2024-11-24 16:07:45 +11:00
"content" : "0"
} ;
if ( ws && ws . readyState === WebSocket . OPEN ) {
ws . send ( JSON . stringify ( stoppedTypingMessage ) ) ;
}
} , 5000 ) ;
}
2025-06-06 10:03:27 +10:00
// Update typing indicator display
2024-11-24 16:07:45 +11:00
function updatePeopleTyping ( ) {
const typingDiv = document . getElementById ( 'typing' ) ;
if ( typingDiv ) {
if ( typingPeople . length === 0 ) {
typingDiv . textContent = '' ;
} else if ( typingPeople . length === 1 ) {
2025-06-06 10:03:27 +10:00
typingDiv . innerHTML = ` <i class="fa-solid fa-keyboard"></i> ${ typingPeople [ 0 ] } is typing... ` ;
2024-11-24 16:07:45 +11:00
} else if ( typingPeople . length === 2 ) {
2025-06-06 10:03:27 +10:00
typingDiv . innerHTML = ` <i class="fa-solid fa-keyboard"></i> ${ typingPeople [ 0 ] } and ${ typingPeople [ 1 ] } are typing... ` ;
2024-11-24 16:07:45 +11:00
} else {
2025-06-06 10:03:27 +10:00
typingDiv . innerHTML = ` <i class="fa-solid fa-keyboard"></i> ${ typingPeople . length } people are typing... ` ;
2024-11-24 16:07:45 +11:00
}
}
}
2025-06-06 10:03:27 +10:00
// Generate random string for Jitsi meeting
function generateRandomString ( length ) {
const characters = 'abcdefghijklmnopqrstuvwxyz' ;
2024-12-04 08:28:53 +11:00
let result = '' ;
const charactersLength = characters . length ;
2025-06-06 10:03:27 +10:00
for ( let i = 0 ; i < length ; i ++ ) {
2024-12-04 08:28:53 +11:00
result += characters . charAt ( Math . floor ( Math . random ( ) * charactersLength ) ) ;
}
return result ;
}
2025-06-06 10:03:27 +10:00
// Start a Jitsi meeting
2024-12-04 08:28:53 +11:00
function startMeeting ( ) {
2025-06-06 10:03:27 +10:00
const link = ` https://meet.jit.si/chookchat- ${ generateRandomString ( 15 ) } ` ;
2024-12-04 08:28:53 +11:00
const processedMessage = {
"type" : "call" ,
"username" : username ,
"token" : md5 ( password ) ,
2025-03-14 08:44:06 +11:00
"room" : currentRoom ,
2024-12-04 08:28:53 +11:00
"content" : link
} ;
2025-06-06 10:03:27 +10:00
2024-12-04 08:28:53 +11:00
if ( ws && ws . readyState === WebSocket . OPEN ) {
ws . send ( JSON . stringify ( processedMessage ) ) ;
2025-06-06 10:03:27 +10:00
window . open ( link , '_blank' ) ;
2024-12-04 08:28:53 +11:00
} else {
2025-06-06 10:03:27 +10:00
alert ( "Something went wrong. Refresh the page and try again." ) ;
2024-12-04 08:28:53 +11:00
}
}
2025-06-06 10:03:27 +10:00
// Update the room list UI
2025-03-14 08:44:06 +11:00
async function updateRoomList ( ) {
2025-06-06 10:03:27 +10:00
const roomListDiv = document . getElementById ( 'room-list' ) ;
if ( ! roomListDiv ) return ;
2025-03-14 08:44:06 +11:00
try {
availableRooms = await getRooms ( ) ;
} catch ( error ) {
console . error ( 'Error updating room list:' , error ) ;
}
2025-06-06 10:03:27 +10:00
// 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 = ` <i class="fa-solid fa-hashtag"></i> ${ room } ` ;
roomElement . addEventListener ( 'click' , ( ) => joinRoom ( room ) ) ;
roomCategory . appendChild ( roomElement ) ;
} ) ;
}
2025-03-14 08:44:06 +11:00
}
2025-06-06 10:03:27 +10:00
// Update current room display
2025-03-14 08:44:06 +11:00
function updateCurrentRoomDisplay ( ) {
2025-06-06 10:03:27 +10:00
const roomNameElement = document . getElementById ( 'current-room-name' ) ;
if ( roomNameElement ) {
roomNameElement . textContent = currentRoom ;
2025-03-14 08:44:06 +11:00
}
2025-06-06 10:03:27 +10:00
// Update active state in room list
2025-03-14 08:44:06 +11:00
const roomItems = document . querySelectorAll ( '.room-item' ) ;
roomItems . forEach ( item => {
2025-06-06 10:03:27 +10:00
if ( item . dataset . room === currentRoom ) {
2025-03-14 08:44:06 +11:00
item . classList . add ( 'active' ) ;
2025-06-06 10:03:27 +10:00
item . classList . remove ( 'unread' ) ;
2025-03-14 08:44:06 +11:00
} else {
item . classList . remove ( 'active' ) ;
}
} ) ;
}
2025-06-06 10:03:27 +10:00
// Clear message display
2025-03-14 08:44:06 +11:00
function clearMessages ( ) {
const messagebox = document . getElementById ( 'messagebox' ) ;
if ( messagebox ) {
messagebox . innerHTML = '' ;
}
}
2025-06-06 10:03:27 +10:00
// Load room message history
2025-03-14 08:44:06 +11:00
async function loadRoomHistory ( roomName ) {
try {
const history = await getRoomHistory ( roomName ) ;
2025-06-07 10:06:46 +10:00
const jHistory = JSON . parse ( history ) ;
2025-03-14 08:44:06 +11:00
if ( history ) {
const messagebox = document . getElementById ( 'messagebox' ) ;
2025-06-07 10:06:46 +10:00
jHistory . forEach ( function ( message , index ) {
// Handle file messages
if ( message . type == "file" ) {
handleFileMessage ( message , true ) ;
return ;
}
// Handle call messages
else if ( message . type == "call" ) {
handleCallMessage ( message ) ;
return ;
}
// 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 , true ) ;
}
} ) ;
/ *
2025-03-14 08:44:06 +11:00
const lines = history . split ( '\n' ) ;
lines . forEach ( line => {
if ( line . trim ( ) ) {
2025-06-06 10:03:27 +10:00
// 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 ) ;
}
2025-03-14 08:44:06 +11:00
}
2025-06-07 10:06:46 +10:00
} ) ; * /
2025-03-14 08:44:06 +11:00
messagebox . scrollTop = messagebox . scrollHeight ;
}
} catch ( error ) {
console . error ( 'Error loading room history:' , error ) ;
}
}
2025-06-06 10:03:27 +10:00
// Prompt to create a new room
2025-03-14 08:44:06 +11:00
function promptCreateRoom ( ) {
const roomName = prompt ( 'Enter a name for the new room:' ) ;
if ( roomName && roomName . trim ( ) ) {
createRoom ( roomName . trim ( ) ) ;
}
}
2025-06-06 10:03:27 +10:00
// Show eggs panel
2024-12-06 13:27:22 +11:00
function showEggs ( ) {
2025-06-06 10:03:27 +10:00
const eggsPanel = document . getElementById ( 'eggs-panel' ) ;
if ( eggsPanel ) {
eggsPanel . classList . remove ( 'hidden' ) ;
eggsPanel . classList . add ( 'visible' ) ;
2024-12-06 13:27:22 +11:00
}
}
2025-06-06 10:03:27 +10:00
// Close eggs panel
2024-12-06 13:27:22 +11:00
function closeEggs ( ) {
2025-06-06 10:03:27 +10:00
const eggsPanel = document . getElementById ( 'eggs-panel' ) ;
if ( eggsPanel ) {
eggsPanel . classList . remove ( 'visible' ) ;
setTimeout ( ( ) => {
eggsPanel . classList . add ( 'hidden' ) ;
} , 200 ) ;
}
2024-12-06 13:27:22 +11:00
}
2025-03-14 08:44:06 +11:00
2025-06-06 10:03:27 +10:00
// 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%) ` ;
2025-03-14 08:44:06 +11:00
}
2025-06-06 10:03:27 +10:00
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 ) ;
}
2025-03-14 08:44:06 +11:00
2025-06-06 10:03:27 +10:00
// Register handler
async function register ( ) {
username = document . getElementById ( 'username' ) . value ;
password = document . getElementById ( 'password' ) . value ;
2024-12-06 13:27:22 +11:00
2025-06-06 10:03:27 +10:00
if ( ! username || ! password ) {
alert ( 'Please enter a username and password' ) ;
return ;
}
// Update button state
const registerButton = document . querySelector ( '.btn-success' ) ;
if ( registerButton ) {
registerButton . innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Registering...' ;
registerButton . disabled = true ;
}
2024-12-06 13:27:22 +11:00
2025-06-06 10:03:27 +10:00
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 = `
< div class = "notepad-container" >
< textarea class = "notepad-textarea" placeholder = "Type your notes here..." > < / t e x t a r e a >
< div class = "notepad-actions" >
< button class = "btn btn-primary" onclick = "saveNotepad()" > Save < / b u t t o n >
< button class = "btn btn-danger" onclick = "clearNotepad()" > Clear < / b u t t o n >
< / d i v >
< / d i v >
` ;
// 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 = "" ;
}
} ;
2025-06-06 17:32:48 +10:00
}