const STATE = { id: 0, chat_id: "", observer: null, is_loading: false, stream_element: null, last_message_time: 0, replying_to: undefined, last_read_receipt_load: 0, }; function create_streamer(chat_id, hook_element) { STATE.chat_id = chat_id; STATE.stream_element = hook_element.parentElement; clear_notifications(); STATE.observer = new IntersectionObserver( (entries) => { load_messages(); }, { root: document.body, rootMargin: "0px", scrollMargin: "0px", threshold: 1.0, }, ); STATE.observer.observe(hook_element); read_receipt(); } function load_messages() { if (STATE.is_loading) { return; } STATE.is_loading = true; STATE.id += 1; fetch( `/chats/_templates/chat/${STATE.chat_id}/messages/before/${STATE.last_message_time}?use_id=${STATE.id}`, ) .then((res) => res.text()) .then((res) => { setTimeout(() => { STATE.is_loading = false; }, 2000); STATE.stream_element.innerHTML += res; STATE.last_message_time = Number.parseInt( document .getElementById(`msgs_data_${STATE.id}`) .getAttribute("data-first-message-time"), ); if (document.getElementById(`msgs_quit_${STATE.id}`)) { STATE.observer.disconnect(); console.log("quit"); } else { STATE.observer.unobserve( STATE.stream_element.querySelector( "[ui_ident=data_marker]", ), ); STATE.stream_element .querySelector("[ui_ident=data_marker]") .remove(); const element = document.createElement("div"); element.setAttribute("ui_ident", "data_marker"); STATE.stream_element.append(element); STATE.observer.observe(element); } }); } function render_message(id) { STATE.is_loading = true; fetch(`/chats/_templates/message/${id}`) .then((res) => res.text()) .then((res) => { STATE.is_loading = false; STATE.stream_element.innerHTML = `${res}${STATE.stream_element.innerHTML}`; mark_message_read(); scroll_bottom(); if (document.getElementById("delivered_read_status")) { document.getElementById("delivered_read_status").remove(); // clear read receipt box } }); } function scroll_bottom() { STATE.stream_element.parentElement.scrollTo( 0, STATE.stream_element.parentElement.scrollHeight, ); } function sock_con() { const socket = new WebSocket( `//${window.location.origin.split("//")[1]}/api/v1/chats/${STATE.chat_id}/_connect`, ); socket.addEventListener("message", async (event) => { if (event.data === "Ping") { return socket.send("Pong"); } const msg = JSON.parse(event.data); if (msg.method === "MessageCreate") { render_message(msg.body); setTimeout(() => { clear_notifications(); }, 150); } else if (msg.method === "MessageDelete") { if (document.getElementById(`message_${msg.body}`)) { document.getElementById(`message_${msg.body}`).remove(); } } else if (msg.method === "MessageUpdate") { const [id, content] = JSON.parse(msg.body); document.getElementById(`${id}_body`).innerHTML = await render_markdown(content); } else if (msg.method === "ReadReceipt") { setTimeout(() => { read_receipt(); }, 150); } }); } function create_message(e) { e.preventDefault(); const body = new FormData(); // attach images if (e.target.images) { for (const file of e.target.images.files) { body.append(file.name, file); } } // add json body body.append( "body", JSON.stringify({ content: e.target.content.value, replying_to: STATE.replying_to, }), ); // send request fetch(`/api/v1/messages/${STATE.chat_id}`, { method: "POST", body }) .then((res) => res.json()) .then((res) => { if (res.ok) { e.target.reset(); document.getElementById("images_zone").classList.add("hidden"); clear_replying_to(); } else { show_message(res.message, res.ok); } }); } function mark_message_read() { fetch(`/api/v1/chats/${STATE.chat_id}/read_message`, { method: "POST", }) .then((res) => res.json()) .then((res) => { if (!res.ok) { show_message(res.message, res.ok); } }); } function read_receipt() { if (new Date().getTime() - STATE.last_read_receipt_load < 150) { console.log("too soon"); return; } STATE.last_read_receipt_load = new Date().getTime(); if (document.getElementById("delivered_read_status")) { document.getElementById("delivered_read_status").remove(); } fetch(`/chats/_templates/chat/${STATE.chat_id}/read_receipt`) .then((res) => res.text()) .then((res) => { document.getElementById("read_receipt_zone").innerHTML = res; }); } function delete_message(id) { if (!confirm("Are you sure you want to do this?")) { return; } fetch(`/api/v1/messages/${id}`, { method: "DELETE", }) .then((res) => res.json()) .then((res) => { if (!res.ok) { show_message(res.message, res.ok); } }); } async function render_markdown(content) { return await ( await fetch("/api/v1/markdown", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content }), }) ).text(); } function edit_message_ui(id) { document.getElementById(`${id}_body`).classList.add("hidden"); document.getElementById(`${id}_edit_area`).classList.remove("hidden"); } function edit_message(id, e) { e.preventDefault(); fetch(`/api/v1/messages/${id}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ content: e.target.content.value, }), }) .then((res) => res.json()) .then(async (res) => { if (!res.ok) { show_message(res.message, res.ok); } else { document .getElementById(`${id}_body`) .classList.remove("hidden"); document .getElementById(`${id}_edit_area`) .classList.add("hidden"); } }); } function leave_chat(id) { if (!confirm("Are you sure you would like to do this?")) { return; } fetch(`/api/v1/chats/${id}/leave`, { method: "POST", }) .then((res) => res.json()) .then((res) => { show_message(res.message, res.ok); }); } function add_member_to_chat(e, chat_id) { e.preventDefault(); document.getElementById("add_user_dialog").close(); fetch(`/api/v1/chats/${chat_id}/members/add/${e.target.username.value}`, { method: "POST", }) .then((res) => res.json()) .then((res) => { show_message(res.message, res.ok); }); } function update_chat_info(id, info) { fetch(`/api/v1/chats/${id}/info`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ info, }), }) .then((res) => res.json()) .then((res) => { show_message(res.message, res.ok); }); } function rename_chat(id, info) { const new_name = prompt("New name:"); if (!new_name) { return; } info.name = new_name; update_chat_info(id, info); } function remove_member_from_chat(chat_id, uid) { if (!confirm("Are you sure you would like to do this?")) { return; } fetch(`/api/v1/chats/${chat_id}/members/remove/${uid}`, { method: "POST", }) .then((res) => res.json()) .then((res) => { show_message(res.message, res.ok); }); } function create_direct_chat_with_user(id) { fetch(`/api/v1/chats`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ style: "Direct", members: [id], }), }) .then((res) => res.json()) .then((res) => { if (!res.ok) { show_message(res.message, res.ok); } else { window.location.href = `/chats/${res.payload}`; } }); } function pin_message(e, id) { fetch(`/api/v1/chats/${STATE.chat_id}/pins/${id}`, { method: "POST", }) .then((res) => res.json()) .then((res) => { show_message(res.message, res.ok); if (res.ok) { e.target.remove(); } }); } function unpin_message(id) { fetch(`/api/v1/chats/${STATE.chat_id}/pins/${id}`, { method: "DELETE", }) .then((res) => res.json()) .then((res) => { show_message(res.message, res.ok); }); } function display_pending_images(e) { document.getElementById("images_zone").innerHTML = ""; document.getElementById("images_zone").classList.remove("hidden"); if (e.target.files.length < 1) { document.getElementById("images_zone").classList.add("hidden"); return; } let idx = 0; for (const file of e.target.files) { document.getElementById("images_zone").innerHTML += ``; idx += 1; } } function remove_file_from_picker(input_id, idx) { const input = document.getElementById(input_id); const files = Array.from(input.files); files.splice(idx - 1, 1); // update files const list = new DataTransfer(); for (item of files) { list.items.add(item); } input.files = list.files; // render display_pending_images({ target: input }); } function reply_to_message(id) { STATE.replying_to = id; document.getElementById("replying_to_zone").classList.remove("hidden"); document.getElementById(`message_${id}`).classList.add("card"); document.getElementById(`message_${id}`).classList.add("surface"); scroll_bottom(); } function clear_replying_to() { if (STATE.replying_to) { document .getElementById(`message_${STATE.replying_to}`) .classList.remove("card"); document .getElementById(`message_${STATE.replying_to}`) .classList.remove("surface"); } STATE.replying_to = undefined; document.getElementById("replying_to_zone").classList.add("hidden"); } function clear_notifications() { fetch(`/api/v1/chats/${STATE.chat_id}/notifications`, { method: "DELETE", }) .then((res) => res.json()) .then((res) => { if (!res.ok) { show_message(res.message, res.ok); } }); }