Emojis, Markdown and better file uploading

This commit is contained in:
Maxwell 2025-06-06 17:32:48 +10:00
parent 7eba84b6ec
commit 82d18ac74d
3 changed files with 241 additions and 17 deletions

View File

@ -110,7 +110,7 @@
</button>
</div>
<div class="message-input-wrapper">
<input type="text" id="messageInput" placeholder="Send a message..." autofocus>
<textarea style="font-family: sans-serif" id="messageInput" placeholder="Send a message..." autofocus></textarea>
<button class="send-button" onclick="sendMessage()">
<i class="fa-solid fa-paper-plane"></i>
</button>

View File

@ -24,10 +24,17 @@ document.addEventListener('DOMContentLoaded', function() {
chatHeader.insertBefore(menuToggle, chatHeader.firstChild);
}
}
document.getElementById('messageInput').addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});
// Add event listeners
document.getElementById('messageInput').addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
document.getElementById('messageInput').addEventListener('keydown', function(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
sendMessage();
}
});
@ -264,6 +271,218 @@ function resetLoginButton() {
}
}
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// Code blocks (```...```)
html = html.replace(/```([^```]*)```/gs, (match, p1) =>
`<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;
}
// Handle incoming messages
function handleMessage(event) {
if (event.data === "ping") {
@ -618,7 +837,7 @@ function addChatMessage(author, content, isHistory = false) {
// Add message text
const textDiv = document.createElement('div');
textDiv.className = 'message-text';
textDiv.textContent = content;
textDiv.innerHTML = markdownToHtmlDiv(content);
contentDiv.appendChild(textDiv);
messageWrapper.appendChild(avatar);
@ -797,6 +1016,8 @@ async function uploadFile() {
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("room", currentRoom);
formData.append("username", username);
formData.append("token", md5(password));
try {
const response = await fetch(getUploadUrl(), {
@ -807,17 +1028,6 @@ async function uploadFile() {
if (response.ok) {
hideFileUpload();
const processedMessage = {
"type": "message",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": `Sent a file`
}
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(processedMessage));
}
} else {
alert("Failed to upload file. Please try again.");
}
@ -1143,4 +1353,4 @@ if (uploadField) {
this.value = "";
}
};
}
}

View File

@ -798,6 +798,20 @@ fun main(args: Array<String>) {
Files.copy(uploadedFile.content(), filePath)
val room = if (ctx.formParam("room") != null) ctx.formParam("room") else "general"
var username: String = ""
var token: String = ""
if (ctx.formParam("username") != null) {
username = ctx.formParam("username").toString()
} else {
// do error
}
if (ctx.formParam("token") != null) {
token = ctx.formParam("token").toString()
} else {
// do error
}
val processedData = JSONObject().apply {
put("type", "fileStatus")
@ -808,7 +822,7 @@ fun main(args: Array<String>) {
val processedData2 = JSONObject().apply {
put("type", "file")
put("username", "system")
put("username", username)
put("room", room)
put("content", "https://maxwellj.xyz/chookchat/uploads/$newFilename")
}