const time_tick_tock_timeout = 60 * 1000; // 1 minute let last_event_datetime_value = 0; let time_tick_tock_class = "time-tock"; let last_creator_id = null; let user_tick_tock_class = "user-tock"; function render_chat_message(topic_chat_content, event, creator, existing_element) { const event_datetime = datetime_to_local(event.timestamps.created); if (event_datetime.value - last_event_datetime_value > time_tick_tock_timeout) { time_tick_tock_class = time_tick_tock_class === "time-tick" ? "time-tock" : "time-tick"; } last_event_datetime_value = event_datetime.value; if (last_creator_id !== creator.id) { user_tick_tock_class = user_tick_tock_class === "user-tick" ? "user-tock" : "user-tick"; last_creator_id = creator.id; } const message_id = event.id.substring(0, 49); const html_content = `
`; if (existing_element) { const template = document.createElement("template"); template.innerHTML = html_content; existing_element.replaceWith(template.content.firstChild); } else { topic_chat_content.insertAdjacentHTML("beforeend", html_content); } } async function append_chat_events(events) { const topic_chat_content = document.getElementById("topic-chat-content"); let last_message_id = topic_chat_content.dataset.last_message_id ?? ""; for (const event of events) { // if the last message is undefined, it becomes this event, otherwise, if this event's id is newer, // it becomes the latest message last_message_id = event.id > last_message_id && event.id.indexOf("TEMP") !== 0 ? event.id : last_message_id; // if the last message has been updated, update the content's dataset to reflect that if (last_message_id !== topic_chat_content.dataset.last_message_id) { topic_chat_content.dataset.last_message_id = last_message_id; } const creator = await USERS.get(event.creator_id); const existing_element = document.getElementById(`chat-${event.id.substring(0, 49)}`) ?? (event.meta?.temp_id ? document.getElementById(`chat-${event.meta.temp_id}`) : undefined); render_chat_message(topic_chat_content, event, creator, existing_element); } topic_chat_content.scrollTop = topic_chat_content.scrollHeight; } // TODO: we need some abortcontroller handling here or something // similar for when we change topics, this is the most basic // first pass outline let topic_polling_request_abort_controller = null; async function poll_for_new_chat_events() { const topic_chat_content = document.getElementById("topic-chat-content"); const topic_id = document.body.dataset.topic; const last_message_id = topic_chat_content.dataset.last_message_id; if (!topic_id) { return; } const message_polling_url = `/api/topics/${topic_id}/events?type=chat&limit=100&sort=newest&wait=true${last_message_id ? `&after_id=${last_message_id}` : ""}`; topic_polling_request_abort_controller = topic_polling_request_abort_controller || new AbortController(); api.fetch(message_polling_url, { signal: topic_polling_request_abort_controller.signal, }) .then(async (new_events_response) => { const new_events = ((await new_events_response.json()) ?? []).reverse(); await append_chat_events(new_events.toReversed()); poll_for_new_chat_events(topic_id); }) .catch((error) => { // TODO: poll again? back off? console.error(error); }); } async function load_active_topic_for_chat() { const topic_id = document.body.dataset.topic; if (!topic_id) return; const user = document.body.dataset.user ? JSON.parse(document.body.dataset.user) : null; if (!user) return; const topic_chat_content = document.getElementById("topic-chat-content"); if (topic_polling_request_abort_controller) { topic_polling_request_abort_controller.abort(); topic_polling_request_abort_controller = null; delete topic_chat_content.dataset.last_message_id; } const topic_response = await api.fetch(`/api/topics/${topic_id}`); if (!topic_response.ok) { const error = await topic_response.json(); alert(error.message ?? JSON.stringify(error)); return; } const topic = await topic_response.json(); topic_chat_content.innerHTML = ""; const topic_selectors = document.querySelectorAll("li.topic"); for (const topic_selector of topic_selectors) { topic_selector.classList.remove("active"); if (topic_selector.id === `topic-selector-${topic_id}`) { topic_selector.classList.add("active"); } } const events_response = await api.fetch( `/api/topics/${topic_id}/events?type=chat&limit=100&sort=newest`, ); if (!events_response.ok) { const error = await events_response.json(); alert(error.message ?? JSON.stringify(error)); return; } const events = (await events_response.json()).reverse(); await append_chat_events(events); poll_for_new_chat_events(); } document.addEventListener("topic_changed", load_active_topic_for_chat); document.addEventListener("user_logged_in", load_active_topic_for_chat);