diff --git a/README.md b/README.md index 0ce0cef..98b3033 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ kubectl apply -f k8s/telegram-scraper.yaml Then open: ```text -http://:8080 +http://:7887 ``` ### Volumes (Docker) diff --git a/webui/app.js b/webui/app.js index 1fabdfb..f3766dc 100644 --- a/webui/app.js +++ b/webui/app.js @@ -10,6 +10,8 @@ const state = { authStates: {}, }; +const ACTIVE_ACCOUNT_STORAGE_KEY = "telegramScraper.activeAccount"; + /* ── API helper ─────────────────────────────────────── */ async function api(path, options = {}) { @@ -78,6 +80,33 @@ function isAccountAuthorized(auth) { ); } +function saveActiveAccount(accountId) { + if (accountId) { + localStorage.setItem(ACTIVE_ACCOUNT_STORAGE_KEY, accountId); + } +} + +function loadSavedActiveAccount(accounts) { + let savedId = localStorage.getItem(ACTIVE_ACCOUNT_STORAGE_KEY); + if (!savedId) { + savedId = localStorage.getItem("activeAccount"); + if (savedId) { + localStorage.setItem(ACTIVE_ACCOUNT_STORAGE_KEY, savedId); + localStorage.removeItem("activeAccount"); + } + } + if (savedId && accounts.some((account) => account.id === savedId)) { + return savedId; + } + if (savedId) { + localStorage.removeItem(ACTIVE_ACCOUNT_STORAGE_KEY); + } + if (state.activeAccount && accounts.some((account) => account.id === state.activeAccount)) { + return state.activeAccount; + } + return accounts[0]?.id || null; +} + /* ── Account tab management ─────────────────────────── */ function renderAccountTabs(accounts) { @@ -175,9 +204,8 @@ function renderAccountPanel(accountId) { } function switchAccount(accountId) { - if (state.activeAccount === accountId) return; state.activeAccount = accountId; - localStorage.setItem("activeAccount", accountId); + saveActiveAccount(accountId); // Update tabs document.querySelectorAll(".account-tab").forEach((tab) => { @@ -490,9 +518,10 @@ async function loadAccounts() { accounts.forEach((acc) => renderAccountPanel(acc.id)); // Determine active account (persisted across reloads) - const savedId = localStorage.getItem("activeAccount"); - const activeId = (savedId && accounts.some(a => a.id === savedId)) ? savedId : (state.activeAccount || accounts[0].id); - switchAccount(activeId); + const activeId = loadSavedActiveAccount(accounts); + if (activeId) { + switchAccount(activeId); + } // Update settings renderSettingsAccounts(); @@ -524,11 +553,13 @@ function renderSettingsAccounts() { node.querySelector(".account-remove-btn").addEventListener("click", async () => { if (!confirmAction(`Remove account "${acc.label || acc.id}"? All its data will be deleted.`)) return; try { + const removingActive = state.activeAccount === acc.id; await api(`/api/accounts/${acc.id}`, { method: "DELETE" }); - await loadAccounts(); - if (state.activeAccount === acc.id) { + if (removingActive) { state.activeAccount = null; + localStorage.removeItem(ACTIVE_ACCOUNT_STORAGE_KEY); } + await loadAccounts(); } catch (err) { alert(`Failed to remove account: ${err.message}`); } diff --git a/webui/viewer.js b/webui/viewer.js index ae20025..48431d2 100644 --- a/webui/viewer.js +++ b/webui/viewer.js @@ -87,6 +87,25 @@ function scrollToBottom() { setTimeout(go, 150); } +function makeScrollSentinel() { + const sentinel = document.createElement("div"); + sentinel.id = "scroll-sentinel"; + sentinel.className = "scroll-sentinel"; + return sentinel; +} + +function resetMessageView(title = "Select a channel", subtitle = "Reading messages from the local SQLite database.") { + const root = document.getElementById("messages-list"); + if (root) { + root.innerHTML = ""; + root.appendChild(makeScrollSentinel()); + } + document.getElementById("viewer-title").textContent = title; + document.getElementById("viewer-subtitle").textContent = subtitle; + viewerState.oldestMessageId = null; + viewerState.newestMessageId = null; +} + function renderChannelList() { const root = document.getElementById("viewer-channel-list"); if (!root) return; @@ -179,6 +198,13 @@ async function switchViewerAccount(accountId) { renderChannelList(); if (viewerState.channelId) { await loadChannel(viewerState.channelId); + } else { + resetMessageView( + "No channels", + viewerState.accountId + ? "This account has no tracked channels yet." + : "No local legacy channels found." + ); } } @@ -190,10 +216,10 @@ async function loadViewerAccount(params) { } catch { viewerState.accounts = []; } - viewerState.accountId = - requested || - viewerState.accounts[0]?.id || - null; + const requestedExists = requested && viewerState.accounts.some((acc) => acc.id === requested); + viewerState.accountId = requestedExists + ? requested + : viewerState.accounts[0]?.id || null; renderAccountSelector(); @@ -289,8 +315,9 @@ function makeDateSep(text) { function renderMessages(payload, append = false) { const root = document.getElementById("messages-list"); - const sentinel = document.getElementById("scroll-sentinel"); if (!root) return; + let sentinel = document.getElementById("scroll-sentinel"); + if (!sentinel) sentinel = makeScrollSentinel(); const channel = payload.channel; document.getElementById("viewer-title").textContent = @@ -408,14 +435,24 @@ async function main() { 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 || - null; + const requestedChannel = params.get("channel"); + const requestedChannelExists = + requestedChannel && + viewerState.channels.some((channel) => channel.channel_id === requestedChannel); + viewerState.channelId = requestedChannelExists + ? requestedChannel + : viewerState.channels[0]?.channel_id || null; renderChannelList(); if (viewerState.channelId) { await loadChannel(viewerState.channelId); + } else { + resetMessageView( + "No channels", + viewerState.accountId + ? "This account has no tracked channels yet." + : "No local legacy channels found." + ); } setupInfiniteScroll();