217 lines
4.4 KiB
JavaScript
217 lines
4.4 KiB
JavaScript
// coms/client/public/data.js
|
||
// Data module: holds the in‐memory threads, notices and reply state
|
||
|
||
import { notifyNotice } from "./notifications.js";
|
||
|
||
// Map of all messages and notices by ID (string IDs)
|
||
// Client’s own session ID
|
||
let myId = null;
|
||
const threads = new Map();
|
||
|
||
// Map of connected users by unique ID
|
||
const users = new Map();
|
||
|
||
// Ordered list of root‐level IDs (messages and notices, stored as strings)
|
||
const rootIds = [];
|
||
|
||
// Negative counter to generate unique IDs for notices
|
||
let noticeCounter = -1;
|
||
|
||
// ID of the message we’re currently replying to (string or null)
|
||
let replyTo = null;
|
||
|
||
// ID of the message currently focused for navigation (string or null)
|
||
let focusedId = null;
|
||
|
||
/**
|
||
* Set the focused message ID (or null to clear).
|
||
* @param {string|null} id
|
||
*/
|
||
function setFocused(id) {
|
||
focusedId = id;
|
||
}
|
||
/**
|
||
* Get the currently focused message ID.
|
||
* @returns {string|null}
|
||
*/
|
||
function getFocused() {
|
||
return focusedId;
|
||
}
|
||
/**
|
||
* Clear the current focus.
|
||
*/
|
||
function clearFocused() {
|
||
focusedId = null;
|
||
}
|
||
|
||
// -- User management API ---
|
||
|
||
/**
|
||
* Register a user with their unique ID.
|
||
* @param {string} id
|
||
* @param {string} name
|
||
*/
|
||
function addUser(id, name) {
|
||
users.set(id, name);
|
||
}
|
||
|
||
/**
|
||
* Remove a user by their ID.
|
||
* @param {string} id
|
||
*/
|
||
function removeUser(id) {
|
||
users.delete(id);
|
||
}
|
||
|
||
/**
|
||
* Update a user’s name.
|
||
* @param {string} id
|
||
* @param {string} name
|
||
*/
|
||
function updateUser(id, name) {
|
||
users.set(id, name);
|
||
}
|
||
|
||
/**
|
||
* Get a list of {id, name} objects for all users.
|
||
* @returns {{id:string, name:string}[]}
|
||
*/
|
||
function getUsers() {
|
||
return Array.from(users.entries()).map(([id, name]) => ({ id, name }));
|
||
}
|
||
|
||
/**
|
||
* Get grouped display strings: e.g. ["Anon (3x)", "Alice (1x)"]
|
||
* @returns {string[]}
|
||
*/
|
||
function getGroupedUsers() {
|
||
const counts = {};
|
||
for (const name of users.values()) {
|
||
counts[name] = (counts[name] || 0) + 1;
|
||
}
|
||
return Object.entries(counts).map(([name, count]) => {
|
||
const nameSpan = `<span class="name">${new Option(name).innerHTML}</span>`;
|
||
return count > 1 ? `${nameSpan} (${count}x)` : nameSpan;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Add a new chat message to the thread structure.
|
||
* @param {{id: string|number, username: string, content: string, ts: number, parent?: string|number|null, authorId?: string|number|null}} msg
|
||
*/
|
||
function addMessage({
|
||
id,
|
||
username,
|
||
content,
|
||
ts,
|
||
parent = null,
|
||
authorId = null,
|
||
}) {
|
||
const sid = String(id);
|
||
const sparent = parent === null ? null : String(parent);
|
||
threads.set(sid, {
|
||
id: sid,
|
||
username,
|
||
authorId: authorId === null ? null : String(authorId),
|
||
content,
|
||
ts,
|
||
parent: sparent,
|
||
children: [],
|
||
});
|
||
if (sparent !== null && threads.has(sparent)) {
|
||
threads.get(sparent).children.push(sid);
|
||
} else {
|
||
rootIds.push(sid);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Insert a system notice as a root‐level thread item.
|
||
* @param {string} content — HTML or plain text for the notice
|
||
* @param {boolean} [shouldNotify=true] — whether to trigger a browser notification
|
||
*/
|
||
function addNotice(content, shouldNotify = true) {
|
||
const ts = Math.floor(Date.now() / 1000);
|
||
const id = String(noticeCounter--);
|
||
threads.set(id, {
|
||
id,
|
||
username: "",
|
||
content,
|
||
ts,
|
||
parent: null,
|
||
children: [],
|
||
});
|
||
rootIds.push(id);
|
||
if (shouldNotify) notifyNotice(content);
|
||
}
|
||
|
||
/**
|
||
* Reset all data (useful for unit tests or full re‐initialization).
|
||
*/
|
||
function clearData() {
|
||
threads.clear();
|
||
rootIds.length = 0;
|
||
noticeCounter = -1;
|
||
replyTo = null;
|
||
users.clear();
|
||
}
|
||
|
||
/** @returns {Map<string,object>} The threads map. */
|
||
function getThreads() {
|
||
return threads;
|
||
}
|
||
|
||
/** @returns {string[]} The ordered list of root‐level IDs. */
|
||
function getRootIds() {
|
||
return rootIds;
|
||
}
|
||
|
||
/**
|
||
* Set this client’s own session ID.
|
||
* @param {string|number} id
|
||
*/
|
||
function setMyId(id) {
|
||
myId = String(id);
|
||
}
|
||
|
||
/**
|
||
* Get this client’s own session ID.
|
||
* @returns {string|null}
|
||
*/
|
||
function getMyId() {
|
||
return myId;
|
||
}
|
||
|
||
/** @returns {string|null} The current reply‐to ID. */
|
||
function getReplyTo() {
|
||
return replyTo;
|
||
}
|
||
|
||
/** @param {string|null} id — Set the current reply‐to ID. */
|
||
function setReplyTo(id) {
|
||
replyTo = id;
|
||
}
|
||
|
||
export {
|
||
threads,
|
||
rootIds,
|
||
users,
|
||
addMessage,
|
||
addNotice,
|
||
addUser,
|
||
removeUser,
|
||
updateUser,
|
||
getUsers,
|
||
getGroupedUsers,
|
||
clearData,
|
||
getThreads,
|
||
getRootIds,
|
||
getReplyTo,
|
||
setReplyTo,
|
||
getFocused,
|
||
setFocused,
|
||
clearFocused,
|
||
setMyId,
|
||
getMyId,
|
||
};
|