feat: add multi-account message viewer support
- viewer.html shows account name in header
- viewer.js loads account list from /api/accounts, auto-selects first account
- Account-aware API endpoints (/api/accounts/{id}/... or legacy /api/...)
- Chat list redesign with avatar initials, preview text, time, message count
- loadViewerAccount() helper fetches account info and user ID
This commit is contained in:
+74
-12
@@ -1,6 +1,8 @@
|
||||
if ("scrollRestoration" in history) history.scrollRestoration = "manual";
|
||||
|
||||
const viewerState = {
|
||||
accountId: null,
|
||||
accounts: [],
|
||||
channels: [],
|
||||
channelId: null,
|
||||
oldestMessageId: null,
|
||||
@@ -42,6 +44,24 @@ function formatTime(dateStr) {
|
||||
return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
|
||||
}
|
||||
|
||||
function chatListTime(dateStr) {
|
||||
if (!dateStr) return "";
|
||||
const d = new Date(dateStr.replace(" ", "T"));
|
||||
if (isNaN(d.getTime())) return "";
|
||||
const now = new Date();
|
||||
if (d.toDateString() === now.toDateString()) {
|
||||
return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
|
||||
}
|
||||
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
||||
}
|
||||
|
||||
function initials(value) {
|
||||
const source = String(value || "?").replace(/^@/, "").trim();
|
||||
const words = source.split(/\s+/).filter(Boolean);
|
||||
if (words.length > 1) return (words[0][0] + words[1][0]).toUpperCase();
|
||||
return source.slice(0, 2).toUpperCase();
|
||||
}
|
||||
|
||||
function dateKey(dateStr) {
|
||||
if (!dateStr) return "";
|
||||
return dateStr.slice(0, 10);
|
||||
@@ -75,8 +95,14 @@ function renderChannelList() {
|
||||
if (!template) return;
|
||||
viewerState.channels.forEach((channel) => {
|
||||
const node = template.content.firstElementChild.cloneNode(true);
|
||||
const preview =
|
||||
channel.last_message_preview ||
|
||||
(channel.has_database ? "No text in the last saved message" : "No local database yet");
|
||||
node.querySelector(".viewer-channel-avatar").textContent = initials(channel.name || channel.channel_id);
|
||||
node.querySelector(".viewer-channel-name").textContent = channel.name;
|
||||
node.querySelector(".viewer-channel-meta").textContent = `${channel.message_count} messages`;
|
||||
node.querySelector(".viewer-channel-time").textContent = chatListTime(channel.last_date);
|
||||
node.querySelector(".viewer-channel-preview").textContent = preview;
|
||||
node.querySelector(".viewer-channel-count").textContent = String(channel.message_count || 0);
|
||||
if (channel.channel_id === viewerState.channelId) {
|
||||
node.classList.add("active");
|
||||
}
|
||||
@@ -90,6 +116,50 @@ function renderChannelList() {
|
||||
});
|
||||
}
|
||||
|
||||
function messageEndpoint(channelId, before = "") {
|
||||
if (viewerState.accountId) {
|
||||
return `/api/accounts/${encodeURIComponent(viewerState.accountId)}/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`;
|
||||
}
|
||||
return `/api/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`;
|
||||
}
|
||||
|
||||
function channelsEndpoint() {
|
||||
if (viewerState.accountId) {
|
||||
return `/api/accounts/${encodeURIComponent(viewerState.accountId)}/channels`;
|
||||
}
|
||||
return "/api/channels";
|
||||
}
|
||||
|
||||
async function loadViewerAccount(params) {
|
||||
const requested = params.get("account");
|
||||
try {
|
||||
const payload = await api("/api/accounts");
|
||||
viewerState.accounts = payload.accounts || [];
|
||||
} catch {
|
||||
viewerState.accounts = [];
|
||||
}
|
||||
viewerState.accountId =
|
||||
requested ||
|
||||
viewerState.accounts[0]?.id ||
|
||||
null;
|
||||
|
||||
const accountName =
|
||||
viewerState.accounts.find((item) => item.id === viewerState.accountId)?.label ||
|
||||
viewerState.accountId ||
|
||||
"Legacy";
|
||||
const accountEl = document.getElementById("viewer-account-name");
|
||||
if (accountEl) accountEl.textContent = accountName;
|
||||
|
||||
try {
|
||||
const authData = viewerState.accountId
|
||||
? await api(`/api/accounts/${encodeURIComponent(viewerState.accountId)}/auth`)
|
||||
: await api("/api/auth");
|
||||
viewerState.userId = authData.user_id || null;
|
||||
} catch (e) {
|
||||
console.warn("Could not fetch user ID:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function buildMessageNode(message, root) {
|
||||
const template = document.getElementById("message-template");
|
||||
const wrap = template.content.firstElementChild.cloneNode(true);
|
||||
@@ -240,9 +310,7 @@ async function loadChannel(channelId, append = false) {
|
||||
? `&before=${viewerState.oldestMessageId}`
|
||||
: "";
|
||||
try {
|
||||
const payload = await api(
|
||||
`/api/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`
|
||||
);
|
||||
const payload = await api(messageEndpoint(channelId, before));
|
||||
if (append) {
|
||||
const list = document.getElementById("messages-list");
|
||||
const prevScrollHeight = list.scrollHeight;
|
||||
@@ -285,15 +353,9 @@ function setupInfiniteScroll() {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const authData = await api("/api/auth");
|
||||
viewerState.userId = authData.user_id || null;
|
||||
} catch (e) {
|
||||
console.warn("Could not fetch user ID:", e);
|
||||
}
|
||||
|
||||
viewerState.channels = await api("/api/channels");
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
await loadViewerAccount(params);
|
||||
viewerState.channels = await api(channelsEndpoint());
|
||||
viewerState.channelId =
|
||||
params.get("channel") ||
|
||||
viewerState.channels[0]?.channel_id ||
|
||||
|
||||
Reference in New Issue
Block a user