const GUESTBOOK_API = 'https://guestbook.bug-dev-mail.workers.dev'; const EMOTES = { ':happy:': 'images/emotes/joy.gif', ':love:': 'images/emotes/love.gif', ':laugh:': 'images/emotes/laugh.gif', ':shock:': 'images/emotes/astonished.gif', ':hello:': 'images/emotes/wave.gif', ':kiss:': 'images/emotes/kiss.gif', ':clap:': 'images/emotes/applause.gif', ':dance:': 'images/emotes/caramelldansen.gif', ':yippee:': 'images/emotes/yippee.gif', }; function replaceLinks(text) { function cleanUrl(url) { return url.replace(/&/g, '&'); } var result = text.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, function(_, label, url) { return '' + label + ''; }); result = result.replace(/(^|[^"'])(https?:\/\/[^\s<]+)/g, function(_, pre, url) { return pre + '' + url + ''; }); return result; } function replaceEmotes(text) { let result = replaceLinks(text); for (const [code, src] of Object.entries(EMOTES)) { const escaped = code.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); result = result.replace(new RegExp(escaped, 'g'), `${code}`); } return result; } function timeAgo(dateStr) { if (!dateStr.includes('T') && !dateStr.includes('-')) { return dateStr; } const d = new Date(dateStr); const now = new Date(); const diffMs = now - d; const diffMin = Math.floor(diffMs / 60000); const diffHr = Math.floor(diffMs / 3600000); const diffDay = Math.floor(diffMs / 86400000); if (diffMin < 1) return 'just now'; if (diffMin < 60) return diffMin + ' min ago'; if (diffHr < 24) return diffHr + ' hr ago'; if (diffDay === 1) return '1 day ago'; return diffDay + ' days ago'; } function createMessageBubble(msg) { const entry = document.createElement('div'); entry.style.setProperty('--col', '#90c9ff'); const nameDiv = document.createElement('div'); nameDiv.textContent = msg.name || 'Anonymous'; const dateDiv = document.createElement('div'); dateDiv.className = 'gb-date'; dateDiv.textContent = timeAgo(msg.date); const msgDiv = document.createElement('div'); msgDiv.className = 'gb-msg-content'; msgDiv.innerHTML = replaceEmotes(msg.message); entry.appendChild(nameDiv); entry.appendChild(dateDiv); entry.appendChild(msgDiv); const actions = document.createElement('div'); actions.className = 'gb-actions'; const likeBtn = document.createElement('button'); likeBtn.className = 'gb-like-btn'; const likeCount = typeof msg.likes === 'number' ? msg.likes : (Array.isArray(msg.likes) ? msg.likes.length : 0); likeBtn.innerHTML = ` ${likeCount}`; likeBtn.onclick = () => likeMessage(msg.id, likeBtn); actions.appendChild(likeBtn); entry.appendChild(actions); return entry; } async function loadMessages() { const picto = document.querySelector('#pictochat-content .picto'); if (!picto) return; picto.innerHTML = ''; try { const res = await fetch(GUESTBOOK_API + '/messages'); const messages = await res.json(); messages.forEach(msg => { picto.appendChild(createMessageBubble(msg)); }); } catch (e) { const err = document.createElement('div'); err.style.setProperty('--col', '#90c9ff'); const nameDiv = document.createElement('div'); nameDiv.textContent = 'system'; const msgDiv = document.createElement('div'); msgDiv.textContent = 'No one has commented yet. Be the first!'; err.appendChild(nameDiv); err.appendChild(document.createTextNode('')); err.appendChild(document.createElement('br')); err.appendChild(msgDiv); picto.appendChild(err); } } async function sendMessage() { const nameInput = document.getElementById('gb-name'); const msgInput = document.getElementById('gb-message'); const sendBtn = document.getElementById('gb-send'); const message = msgInput.value.trim(); if (!message) return; sendBtn.classList.add('gb-disabled'); try { await fetch(GUESTBOOK_API + '/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: nameInput.value.trim() || 'Anonymous', message: message, }), }); msgInput.value = ''; await loadMessages(); setTimeout(replaceTextWithEmotes, 500); } catch (e) { } sendBtn.classList.remove('gb-disabled'); } async function likeMessage(id, btn) { try { const res = await fetch(GUESTBOOK_API + `/messages/${id}/like`, { method: 'POST' }); const data = await res.json(); btn.innerHTML = ` ${data.likes}`; btn.classList.add('gb-liked'); } catch (e) { } } function toggleEmoteSelector() { const els = document.getElementsByClassName('gb-toggle-div'); for (let i = 0; i < els.length; i++) { if (els[i].style.display === 'none' || els[i].style.display === '') { els[i].style.display = 'block'; } else { els[i].style.display = 'none'; } } } function addText(text) { const textarea = document.getElementById('gb-message'); textarea.value += text; textarea.focus(); } function replaceTextWithEmotes() { const picto = document.querySelector('#pictochat-content .picto'); if (!picto) return; function replaceTextNodes(element) { element.childNodes.forEach(function (node) { if (node.nodeType === Node.TEXT_NODE) { let replacedText = node.textContent; let changed = false; for (const [key, src] of Object.entries(EMOTES)) { const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const imgTag = ""; const newText = replacedText.replace(new RegExp(escapedKey, 'g'), imgTag); if (newText !== replacedText) { replacedText = newText; changed = true; } } if (changed) { const newElement = document.createElement('span'); newElement.innerHTML = replacedText; node.parentNode.replaceChild(newElement, node); } } else { replaceTextNodes(node); } }); } replaceTextNodes(picto); } (function () { const picto = document.querySelector('#pictochat-content .picto'); if (!picto) return; const sendBtn = document.getElementById('gb-send'); if (sendBtn) sendBtn.addEventListener('click', sendMessage); const msgInput = document.getElementById('gb-message'); if (msgInput) { msgInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); } const emoteBtn = document.getElementById('gb-emote-toggle'); if (emoteBtn) emoteBtn.addEventListener('click', toggleEmoteSelector); document.addEventListener('click', function (e) { const selector = document.getElementById('gb-emote-selector'); const toggle = document.getElementById('gb-emote-toggle'); if (selector && selector.style.display === 'block' && !selector.contains(e.target) && e.target !== toggle) { selector.style.display = 'none'; } }); const emoteItems = document.querySelectorAll('#gb-emote-selector .gb-emote-item'); emoteItems.forEach(function (img) { img.addEventListener('click', function () { addText(img.title + ' '); }); }); loadMessages().then(function () { replaceTextWithEmotes(); }); })();