Mercurial Hosting > chat
view src/chat.js @ 78:2a602ef53eef
minor
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Sun, 09 Mar 2025 22:20:36 -0600 |
parents | 624654817f99 |
children | b5a316575e64 |
line wrap: on
line source
'use strict'; let title = document.title; let currentChatId = null; let eventSource; let lastUpdate = Date.now(); let userId; let filebinUrl; function evalEvent(event) { // console.log(event); eval(event.data); } function setUserEventSource(userId) { let userEventSource = new EventSource(`${location.origin}/user/${userId}`); userEventSource.onmessage = evalEvent; } function selectChat(chatId) { document.querySelector('div[chat_content]').setAttribute('show','posts'); if( chatId === currentChatId ) return; let div = document.querySelector(`div[chat="${chatId}"]`); let selected = div.parentNode.querySelector('[selected]'); if( selected ) selected.removeAttribute('selected'); div.setAttribute('selected',''); ajax(`get_chat.js?chat=${chatId}`); currentChatId = chatId; history.replaceState(null,null,`?chat=${chatId}`); clearUnread(); if(eventSource) eventSource.close(); eventSource = new EventSource(`${location.origin}/chat/${chatId}`); eventSource.onmessage = evalEvent; } function back() { document.querySelector('div[chat_content]').setAttribute('show','chats'); } function gotChat(html) { document.querySelector('div[posts]').innerHTML = html; fixPosts(); document.querySelector('div[input] textarea').focus(); document.querySelector('div[input]').scrollIntoView({block: 'end'}); } function fixTextarea(event) { let textarea = event.target; textarea.style.height = 'initial'; textarea.style.height = (textarea.scrollHeight+2) + 'px'; textarea.scrollIntoViewIfNeeded(false); } const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; function addPost() { let textarea = document.querySelector('div[input] textarea'); let text = textarea.value; if( text.trim() === '' ) return; ajax(`add_post.js?chat=${currentChatId}`,`content=${encodeURIComponent(text)}`); textarea.value = ''; } function textareaKey(event) { if( event.keyCode===13 && !event.shiftKey && !event.ctrlKey && !isMobile ) { event.preventDefault(); addPost(); } } function editTextareaKey(event) { if( event.keyCode===13 && !event.shiftKey && !event.ctrlKey && !isMobile ) { event.preventDefault(); savePost(event.target); } } function fixPosts() { let divs = document.querySelectorAll('div[post][fix]'); for( let div of divs ) { let whenSpan = div.querySelector('span[when]'); whenSpan.textContent = new Date(Number(whenSpan.textContent)).toLocaleString([],{dateStyle:'short',timeStyle:'short'}); let textDiv = div.querySelector('div[text]'); textDiv.innerHTML = urlsToLinks(textDiv.innerHTML); if( div.getAttribute('author') === userId ) div.querySelector('span[pulldown]').innerHTML = document.querySelector('div[hidden] span[pulldown]').innerHTML; div.removeAttribute('fix'); } } function deleteChat() { let dialog = document.querySelector('dialog[delete_chat]'); openModal(dialog); } function doDeleteChat(el) { closeModal(el); ajax(`delete_chat.js?chat=${currentChatId}`); } let currentPostId; function getPostId(el) { while(true) { let postId = el.getAttribute('post'); if( postId ) return postId; el = el.parentNode; } } function deletePost(el) { currentPostId = getPostId(el); let dialog = document.querySelector('dialog[delete_post]'); openModal(dialog); } function doDeletePost(el) { closeModal(el); ajax(`delete_post.js?post=${currentPostId}`); } function deleted(postId) { let div = document.querySelector(`div[post="${postId}"]`); if( div ) div.outerHTML = ''; } function editPost(el) { ajax(`edit_post.js?post=${getPostId(el)}`); } function openEditPost(postId,text) { currentPostId = postId; let dialog = document.querySelector('dialog[edit_post]'); let textarea = dialog.querySelector('textarea'); textarea.value = text; openModal(dialog); } function savePost(el) { let text = document.querySelector('dialog[edit_post] textarea').value; closeModal(el); ajax(`save_post.js?post=${currentPostId}`,`content=${encodeURIComponent(text)}`); } function edited(postId,html) { let div = document.querySelector(`div[post="${postId}"]`); if( div ) { div.outerHTML = html; fixPosts(); } } function active() { let url = 'active.js'; if( currentChatId ) url += `?chat=${currentChatId}`; ajax(url); } function added(html) { let input = document.querySelector('div[input]'); input.insertAdjacentHTML('beforebegin',html); fixPosts(); input.scrollIntoView({block: 'end'}); if( document.hasFocus() ) active(); } function getChats(chatId,updated) { let first = document.querySelector('div[chat]'); if( !first || first.getAttribute('chat') !== chatId ) { // console.log('getChats'); ajax('get_chats.js'); } else if( first && currentChatId !== chatId ) { incUnread(first); } if( updated ) lastUpdate = updated; if( !document.hasFocus() ) { document.title = title + ' *'; } } function gotChats(html) { document.querySelector('div[chats]').innerHTML = html; if( currentChatId ) { let current = document.querySelector(`div[chat="${currentChatId}"]`); if( current ) { current.setAttribute('selected',''); current.scrollIntoViewIfNeeded(false); clearUnread(); } else { currentChatId = null; document.querySelector('div[posts]').innerHTML = ''; } } } window.onfocus = function() { // console.log('onfocus'); document.title = title; active(); }; let urlRegex = /(^|\s)(https?:\/\/\S+)/g; let filebinUrlRegex = /(^|\s)(https:\/\/filebin.net\/[0-9a-f]+\/(\S+))/g; function urlsToLinks(text) { text = text.replace( filebinUrlRegex, '$1<a target="_blank" href="$2">$3</a>' ); text = text.replace( urlRegex, '$1<a target="_blank" href="$2">$2</a>' ); return text; } let currentPulldown = null; let newPulldown = null; function clickMenu(clicked,display) { //console.log("clickMenu"); let pulldown = clicked.parentNode.querySelector('div'); if( pulldown !== currentPulldown ) { pulldown.style.display = display || "block"; newPulldown = pulldown; window.onclick = function() { //console.log("window.onclick"); if( currentPulldown ) { currentPulldown.style.display = "none"; if( !newPulldown ) window.onclick = null; } currentPulldown = newPulldown; newPulldown = null; }; pulldown.scrollIntoViewIfNeeded(false); } } function heartbeat() { let url = `heartbeat.js?last_update=${lastUpdate}`; if( currentChatId ) url += `&chat=${currentChatId}`; ajax(url); } setInterval( heartbeat, 10000 ); heartbeat(); let online = {}; function showOnline() { let old = Date.now() - 70000; let spans = document.querySelectorAll('span[online]'); for( let span of spans ) { let id = span.getAttribute('online'); let wasOnline = online[id]; let isOnline = !!wasOnline && wasOnline > old; span.setAttribute('is_online',isOnline); } } function clearUnread() { let span = document.querySelector(`div[chat="${currentChatId}"] span[unread]`); span.setAttribute('unread','0'); span.textContent = '0'; } function incUnread(div) { let span = div.querySelector('span[unread]'); let n = parseInt(span.getAttribute('unread')) + 1; span.setAttribute('unread',n); span.textContent = n; } function invite() { let email = document.querySelector('input[type=email]').value; ajax(`invite.js?email=${encodeURIComponent(email)}`); } function openInvite(email) { let dialog = document.querySelector('dialog[invite]'); let span = dialog.querySelector('span[email]'); span.textContent = email; openModal(dialog); } function gotoInvite(el) { let email = document.querySelector('dialog[invite] span[email]').textContent; closeModal(el); location = `chat?with=${email}`; ajax(`save_post.js?post=${currentPostId}`,`content=${encodeURIComponent(text)}`); } function uploadFile() { document.querySelector('input[type="file"]').click(); } function addFileUrl(url) { let textarea = document.querySelector('div[input] textarea'); let text = textarea.value; if( /\S$/.test(text) ) text += ' '; text += url; textarea.value = text; } function uploadToFilebin(fileName,fileContent) { //console.log(fileContent.byteLength); let request = new XMLHttpRequest(); let url = filebinUrl + fileName; request.open( 'POST', url ); request.onload = function() { if( request.status !== 201 ) { let err = 'upload failed: ' + request.status; if( request.responseText ) { err += '\n' + request.responseText; } console.log(err); ajax( '/error_log.js', 'err='+encodeURIComponent(err) ); return; } addFileUrl(url); }; request.send(fileContent); } function loadedFile(input) { let file = input.files[0]; input.value = null; console.log(file); let reader = new FileReader(); reader.onload = function() { uploadToFilebin(file.name,reader.result); }; reader.readAsArrayBuffer(file); } let times = [ { time: 1000*60*60*24, unit: 'day' }, { time: 1000*60*60, unit: 'hour' }, { time: 1000*60, unit: 'minute' } ]; function ago(time) { for( let t of times ) { let n = Math.floor(time / t.time); if( n > 0 ) { let s = `${n} ${t.unit}`; if( n > 1 ) s = s + 's'; return s + ' ago'; } } return 'just now'; end } function openPeople() { let dialog = document.querySelector('dialog[people]'); let spans = dialog.querySelectorAll('span[last_seen]'); let now = Date.now(); let old = now - 70000; for( let span of spans ) { let id = span.getAttribute('last_seen'); let s; let lastOnline = online[id]; if( !lastOnline ) { s = ''; } else if( lastOnline > old ) { s = 'Active now'; } else { s = 'Last seen ' + ago(now - lastOnline); } console.log(`${id} ${s}`); span.textContent = s; } openModal(dialog); } function readUpTo(userId,userNameHtml,unread) { //console.log(`readUpTo ${unread}`); let divs = document.querySelectorAll('div[post]'); if( unread >= divs.length ) return; let div = divs[divs.length - unread - 1]; let old = document.querySelector(`div[unread][user="${userId}"]`); if( old ) { //console.log(`was ${div.getAttribute('unread')}`); if( div == old.parentNode ) return; old.outerHTML = ''; } //console.log('readUpTo'); let html = `<div user="${userId}" unread="${unread}">read by ${userNameHtml}</div>`; div.insertAdjacentHTML('beforeend',html); }