This commit is contained in:
2026-03-11 01:09:12 -04:00
parent dd60be6d2c
commit 064e195f2a
4 changed files with 84 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ import {
} from "./data.js";
import { renderChat } from "./render.js";
import { initWebSocket } from "./wsModule.js";
import { primeNotifications } from "./notifications.js";
document.addEventListener("DOMContentLoaded", () => {
const chat = document.getElementById("chat");
@@ -25,6 +26,7 @@ document.addEventListener("DOMContentLoaded", () => {
chat.style.display = "";
inputContainer.style.display = "flex";
primeNotifications();
const defaultName = "Anon";
const MAX_NAME_LEN = 15; // server limit is NAME_MAX_LENGTH-1 (15 chars)

View File

@@ -1,6 +1,8 @@
// coms/client/public/data.js
// Data module: holds the inmemory threads, notices and reply state
import { notifyNotice } from "./notifications.js";
// Map of all messages and notices by ID (string IDs)
// Clients own session ID
let myId = null;
@@ -139,6 +141,7 @@ function addNotice(content) {
children: [],
});
rootIds.push(id);
notifyNotice(content);
}
/**

View File

@@ -0,0 +1,59 @@
// Simple notification helpers for the chat client.
const APP_NAME = "COMS";
function supportsNotifications() {
return typeof Notification !== "undefined";
}
function stripHtml(input) {
const div = document.createElement("div");
div.innerHTML = input;
return div.textContent || div.innerText || "";
}
/**
* Ask for permission if not already granted/denied.
*/
function primeNotifications() {
if (!supportsNotifications()) return;
if (Notification.permission === "default") {
Notification.requestPermission();
}
}
function showNotification(title, body) {
if (!supportsNotifications()) return;
const options = { body };
if (Notification.permission === "granted") {
new Notification(title, options);
} else if (Notification.permission === "default") {
Notification.requestPermission().then((perm) => {
if (perm === "granted") new Notification(title, options);
});
}
}
/**
* Notify the user of a system notice.
* @param {string} content - HTML or text content of the notice.
*/
function notifyNotice(content) {
const text = stripHtml(content);
if (!text) return;
showNotification(`${APP_NAME} notice`, text);
}
/**
* Notify the user that someone replied directly to their thread.
* @param {string} authorName
* @param {string} content
*/
function notifyDirectReply(authorName, content) {
const text = stripHtml(content);
if (!text) return;
const name = authorName || "Anon";
showNotification(`${name} replied to your thread`, text);
}
export { primeNotifications, notifyNotice, notifyDirectReply };

View File

@@ -13,12 +13,15 @@ import {
updateUser,
getGroupedUsers,
setMyId,
getMyId,
getThreads,
clearData,
users,
setFocused,
setReplyTo,
} from "./data.js";
import { renderChat } from "./render.js";
import { notifyDirectReply } from "./notifications.js";
/**
* Initialize WebSocket connection and wire up event handlers.
@@ -132,6 +135,23 @@ export function initWebSocket(
? null
: String(parent),
});
// Notify when someone else replies directly to one of my root messages.
const myId = getMyId();
const parentId =
parent === null || parent === undefined || parent === "-1"
? null
: String(parent);
if (myId && parentId && author?.id && String(author.id) !== myId) {
const parentMsg = getThreads().get(parentId);
if (
parentMsg &&
parentMsg.parent === null &&
parentMsg.authorId === myId
) {
notifyDirectReply(username, content);
}
}
break;
}
case "msg_ack": {