mirror of
https://github.com/sartoopjj/thefeed.git
synced 2026-05-18 04:14:36 +03:00
fix: improve channel selection hints and profile switching logic
This commit is contained in:
@@ -3022,7 +3022,7 @@
|
||||
ok: 'باشه',
|
||||
cancel_media_msg: 'دانلود این رسانه لغو شود؟', dismiss: 'بستن',
|
||||
write_message: 'پیام بنویسید...', configure_server: 'برای شروع یک سرور راهاندازی کنید',
|
||||
set_up: 'راهاندازی', switching: 'در حال تغییر پروفایل...',
|
||||
set_up: 'راهاندازی', switching: 'در حال تغییر پروفایل...', select_channel_hint: 'یک کانال را برای دیدن پیامها انتخاب کنید',
|
||||
font_size: 'اندازه قلم', debug_mode: 'حالت دیباگ', language: 'زبان',
|
||||
next_fetch_info: 'زمان باقیمانده تا دریافت بعدی محتوا توسط سرور',
|
||||
no_profiles: 'هنوز پروفایلی وجود ندارد', add_profile: '+ پروفایل جدید',
|
||||
@@ -3232,7 +3232,7 @@
|
||||
ok: 'OK',
|
||||
cancel_media_msg: 'Cancel this download?', dismiss: 'Dismiss',
|
||||
write_message: 'Write a message...', configure_server: 'Configure a server to start reading',
|
||||
set_up: 'Set Up', switching: 'Switching profile...',
|
||||
set_up: 'Set Up', switching: 'Switching profile...', select_channel_hint: 'Pick a channel to view its messages',
|
||||
font_size: 'Font Size', debug_mode: 'Debug mode', language: 'Language',
|
||||
next_fetch_info: 'Time until the server next fetches fresh channel content',
|
||||
no_profiles: 'No profiles yet', add_profile: '+ Add Profile',
|
||||
@@ -3549,7 +3549,11 @@
|
||||
// Land on the channel list; don't auto-open the first channel.
|
||||
// Users on mobile may be tapping while we load and the auto-open
|
||||
// races with their touch.
|
||||
if (!channels || channels.length === 0) { showInitProgress(); await doRefresh(); }
|
||||
if (!channels || channels.length === 0) {
|
||||
showInitProgress(); await doRefresh();
|
||||
} else {
|
||||
document.getElementById('messages').innerHTML = '<div class="empty-state"><p>' + (t('select_channel_hint') || '') + '</p></div>';
|
||||
}
|
||||
startAutoRefresh();
|
||||
} catch (e) { }
|
||||
}
|
||||
@@ -4758,14 +4762,20 @@
|
||||
var r = await fetch('/api/profiles/switch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id, skipCheck: skipCheck }) });
|
||||
if (!r.ok) return;
|
||||
activeProfileId = id; selectedChannel = 0; channels = [];
|
||||
refreshingChannels = {};
|
||||
progressSilencedUntil = Date.now() + 5000;
|
||||
// Reset the chat header so it doesn't keep showing the previous
|
||||
// profile's channel name/handle while the user picks a new one.
|
||||
document.getElementById('chatName').textContent = 'thefeed';
|
||||
document.getElementById('chatSub').textContent = '';
|
||||
document.getElementById('progressPanel').innerHTML = '';
|
||||
document.getElementById('messages').innerHTML = '<div class="empty-state"><p>' + t('switching') + '</p></div>';
|
||||
document.getElementById('messages').innerHTML = '<div class="empty-state"><p>' + (t('select_channel_hint') || t('switching')) + '</p></div>';
|
||||
// On desktop the chat panel is always visible; collapse it back to
|
||||
// the sidebar so the empty state doesn't sit next to a stale header.
|
||||
openSidebar();
|
||||
await loadProfiles(); closeProfiles();
|
||||
// selectChannel races with in-flight touch on mobile, skip there.
|
||||
var preLoadIsOnSidebar = !chatIsOpen;
|
||||
await loadChannels();
|
||||
if (channels.length === 0) { showInitProgress(); await doRefresh(); return; }
|
||||
if (preLoadIsOnSidebar && !chatIsOpen && !mobileQuery.matches) await selectChannel(1);
|
||||
if (channels.length === 0) { showInitProgress(); await doRefresh(); }
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
@@ -6904,7 +6914,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
var progressSilencedUntil = 0;
|
||||
function updateProgressDisplay(line) {
|
||||
// Silence briefly across a profile switch — log events from the
|
||||
// previous profile's in-flight fetch keep arriving until the old
|
||||
// fetcher's context cancellation lands.
|
||||
if (Date.now() < progressSilencedUntil) return;
|
||||
var match = line.match(/Channel\s+(\d+)/); if (!match) return;
|
||||
var channelNum = parseInt(match[1]);
|
||||
var fracMatch = line.match(/\((\d+)\/(\d+)\)/);
|
||||
|
||||
+31
-22
@@ -2279,12 +2279,13 @@ func addToBank(pl *ProfileList, resolvers []string) int {
|
||||
}
|
||||
|
||||
// persistResolverScores saves the current fetcher stats to profiles.json.
|
||||
// Not serialised by profilesMu — initFetcher holds s.mu while calling this,
|
||||
// and grabbing profilesMu here would risk AB-BA with handlers that take
|
||||
// profilesMu first. The score map-merge is benign under last-writer-wins.
|
||||
func (s *Server) persistResolverScores(stats map[string][3]int64) {
|
||||
if len(stats) == 0 {
|
||||
return
|
||||
}
|
||||
s.profilesMu.Lock()
|
||||
defer s.profilesMu.Unlock()
|
||||
pl, err := s.loadProfiles()
|
||||
if err != nil || pl == nil {
|
||||
return
|
||||
@@ -2363,9 +2364,9 @@ func (s *Server) handleProfiles(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
s.profilesMu.Lock()
|
||||
defer s.profilesMu.Unlock()
|
||||
pl, err := s.loadProfilesExisting()
|
||||
if err != nil {
|
||||
s.profilesMu.Unlock()
|
||||
http.Error(w, fmt.Sprintf("load: %v", err), 500)
|
||||
return
|
||||
}
|
||||
@@ -2444,34 +2445,44 @@ func (s *Server) handleProfiles(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
default:
|
||||
s.profilesMu.Unlock()
|
||||
http.Error(w, "unknown action", 400)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.saveProfiles(pl); err != nil {
|
||||
http.Error(w, fmt.Sprintf("save profiles: %v", err), 500)
|
||||
return
|
||||
}
|
||||
|
||||
// Only re-init the fetcher when the active profile's config was modified.
|
||||
saveErr := s.saveProfiles(pl)
|
||||
var activeConfig *Config
|
||||
if needsReinit && pl.Active != "" {
|
||||
for _, p := range pl.Profiles {
|
||||
if p.ID == pl.Active {
|
||||
_ = s.saveConfig(&p.Config)
|
||||
s.mu.Lock()
|
||||
s.config = &p.Config
|
||||
s.mu.Unlock()
|
||||
if err := s.initFetcher(); err != nil {
|
||||
log.Printf("[web] re-init fetcher after profile change: %v", err)
|
||||
} else if req.SkipCheck {
|
||||
s.skipCheckerUseSaved()
|
||||
} else {
|
||||
s.startCheckerThenRefresh()
|
||||
}
|
||||
cfg := p.Config
|
||||
activeConfig = &cfg
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
s.profilesMu.Unlock()
|
||||
|
||||
if saveErr != nil {
|
||||
http.Error(w, fmt.Sprintf("save profiles: %v", saveErr), 500)
|
||||
return
|
||||
}
|
||||
|
||||
// initFetcher takes s.mu — call it OUTSIDE profilesMu so handlers
|
||||
// that need both don't AB-BA against it.
|
||||
if activeConfig != nil {
|
||||
_ = s.saveConfig(activeConfig)
|
||||
s.mu.Lock()
|
||||
s.config = activeConfig
|
||||
s.mu.Unlock()
|
||||
if err := s.initFetcher(); err != nil {
|
||||
log.Printf("[web] re-init fetcher after profile change: %v", err)
|
||||
} else if req.SkipCheck {
|
||||
s.skipCheckerUseSaved()
|
||||
} else {
|
||||
s.startCheckerThenRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]any{"ok": true, "profiles": pl})
|
||||
|
||||
@@ -2976,8 +2987,6 @@ func (s *Server) persistLastScanToProfiles(resolvers []string) {
|
||||
// a populated saved list was applied; false routes the caller to the
|
||||
// last_scan.json / full-scan fallback chain.
|
||||
func (s *Server) applySelectedList() bool {
|
||||
s.profilesMu.Lock()
|
||||
defer s.profilesMu.Unlock()
|
||||
pl, err := s.loadProfiles()
|
||||
if err != nil || pl == nil {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user