From a2a711302378771458f0f1200713671d1f818d8a Mon Sep 17 00:00:00 2001 From: HuberttFox Date: Thu, 12 Mar 2026 15:35:16 +0800 Subject: [PATCH 1/5] fix: use project icon and resilient nginx proxy --- index.html | 5 +++-- nginx.conf | 6 ++++-- src/components/LoginScreen.tsx | 12 ++++++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index f40da33..dcd84d7 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,8 @@ - + + GitHub Stars Manager - AI-Powered Repository Management @@ -15,4 +16,4 @@
- \ No newline at end of file + diff --git a/nginx.conf b/nginx.conf index 084e279..dbd565e 100644 --- a/nginx.conf +++ b/nginx.conf @@ -5,6 +5,7 @@ events { http { include /etc/nginx/mime.types; default_type application/octet-stream; + resolver 127.0.0.11 ipv6=off valid=30s; # Hide nginx version server_tokens off; @@ -32,7 +33,8 @@ http { server_name localhost; # Backend API proxy location /api/ { - proxy_pass http://backend:3000/api/; + set $backend_upstream backend:3000; + proxy_pass http://$backend_upstream; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -86,4 +88,4 @@ http { internal; } } -} \ No newline at end of file +} diff --git a/src/components/LoginScreen.tsx b/src/components/LoginScreen.tsx index 8299d4d..f289578 100644 --- a/src/components/LoginScreen.tsx +++ b/src/components/LoginScreen.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Star, Github, Key, ArrowRight, AlertCircle } from 'lucide-react'; +import { Github, Key, ArrowRight, AlertCircle } from 'lucide-react'; import { useAppStore } from '../store/useAppStore'; import { GitHubApiService } from '../services/githubApi'; @@ -67,8 +67,12 @@ export const LoginScreen: React.FC = () => {
-
- +
+ GitHub Stars Manager

GitHub Stars Manager @@ -177,4 +181,4 @@ export const LoginScreen: React.FC = () => {

); -}; \ No newline at end of file +}; From 6ceeb50dc9b76e3488ca73e1d8c1ac4195584c98 Mon Sep 17 00:00:00 2001 From: HuberttFox Date: Thu, 12 Mar 2026 15:38:42 +0800 Subject: [PATCH 2/5] fix: preserve loaded repository cards during sync --- src/components/RepositoryList.tsx | 43 +++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/components/RepositoryList.tsx b/src/components/RepositoryList.tsx index 665e313..beabbc9 100644 --- a/src/components/RepositoryList.tsx +++ b/src/components/RepositoryList.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useMemo } from 'react'; import { Bot, ChevronDown, Pause, Play } from 'lucide-react'; import { RepositoryCard } from './RepositoryCard'; @@ -85,12 +85,45 @@ export const RepositoryList: React.FC = ({ const startIndex = filteredRepositories.length === 0 ? 0 : 1; const endIndex = Math.min(visibleCount, filteredRepositories.length); const visibleRepositories = filteredRepositories.slice(0, visibleCount); - - // Reset visible count when filters or data change + const filterResetKey = useMemo(() => JSON.stringify({ + selectedCategory, + query: searchFilters.query, + languages: searchFilters.languages, + tags: searchFilters.tags, + platforms: searchFilters.platforms, + sortBy: searchFilters.sortBy, + sortOrder: searchFilters.sortOrder, + minStars: searchFilters.minStars, + maxStars: searchFilters.maxStars, + isAnalyzed: searchFilters.isAnalyzed, + isSubscribed: searchFilters.isSubscribed, + }), [ + selectedCategory, + searchFilters.query, + searchFilters.languages, + searchFilters.tags, + searchFilters.platforms, + searchFilters.sortBy, + searchFilters.sortOrder, + searchFilters.minStars, + searchFilters.maxStars, + searchFilters.isAnalyzed, + searchFilters.isSubscribed, + ]); + + // Reset visible count only when filter context changes. useEffect(() => { setVisibleCount(LOAD_BATCH); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedCategory, repositories, filteredRepositories.length]); + }, [filterResetKey]); + + // Clamp visible count when result set becomes smaller, but do not collapse + // back to the initial batch during backend sync refreshes. + useEffect(() => { + setVisibleCount((count) => { + if (filteredRepositories.length === 0) return LOAD_BATCH; + return Math.min(count, filteredRepositories.length); + }); + }, [filteredRepositories.length]); // IntersectionObserver to load more on demand useEffect(() => { From 99545c2bb169192c1f522e939db06f00540606e2 Mon Sep 17 00:00:00 2001 From: HuberttFox Date: Thu, 12 Mar 2026 15:44:02 +0800 Subject: [PATCH 3/5] fix: preserve scroll position after repository sync --- src/components/RepositoryList.tsx | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/components/RepositoryList.tsx b/src/components/RepositoryList.tsx index beabbc9..37df7c7 100644 --- a/src/components/RepositoryList.tsx +++ b/src/components/RepositoryList.tsx @@ -34,6 +34,8 @@ export const RepositoryList: React.FC = ({ const [showDropdown, setShowDropdown] = useState(false); const [isPaused, setIsPaused] = useState(false); const [disableCardAnimations, setDisableCardAnimations] = useState(false); + const savedScrollYRef = useRef(null); + const restoreScrollFrameRef = useRef(null); // 使用 useRef 来管理停止状态,确保在异步操作中能正确访问最新值 const shouldStopRef = useRef(false); @@ -150,11 +152,35 @@ export const RepositoryList: React.FC = ({ useEffect(() => { const handleSyncVisualState = (event: Event) => { const customEvent = event as CustomEvent<{ isSyncing?: boolean }>; - setDisableCardAnimations(!!customEvent.detail?.isSyncing); + const isSyncing = !!customEvent.detail?.isSyncing; + setDisableCardAnimations(isSyncing); + + if (isSyncing) { + savedScrollYRef.current = window.scrollY; + if (restoreScrollFrameRef.current !== null) { + cancelAnimationFrame(restoreScrollFrameRef.current); + restoreScrollFrameRef.current = null; + } + return; + } + + const targetScrollY = savedScrollYRef.current; + if (targetScrollY === null) return; + + restoreScrollFrameRef.current = window.requestAnimationFrame(() => { + restoreScrollFrameRef.current = window.requestAnimationFrame(() => { + window.scrollTo({ top: targetScrollY, behavior: 'auto' }); + restoreScrollFrameRef.current = null; + savedScrollYRef.current = null; + }); + }); }; window.addEventListener('gsm:repository-sync-visual-state', handleSyncVisualState as EventListener); return () => { + if (restoreScrollFrameRef.current !== null) { + cancelAnimationFrame(restoreScrollFrameRef.current); + } window.removeEventListener('gsm:repository-sync-visual-state', handleSyncVisualState as EventListener); }; }, []); From 3ad9067f9957a7d8cc79c78c93a67a82925e555c Mon Sep 17 00:00:00 2001 From: HuberttFox Date: Thu, 12 Mar 2026 16:47:30 +0800 Subject: [PATCH 4/5] fix: preserve category view across refresh --- src/App.tsx | 8 ++++---- src/components/RepositoryList.tsx | 8 ++++++++ src/store/useAppStore.ts | 7 +++++++ src/types/index.ts | 1 + 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9324b5d..660521e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { LoginScreen } from './components/LoginScreen'; import { Header } from './components/Header'; import { SearchBar } from './components/SearchBar'; @@ -16,13 +16,13 @@ function App() { const { isAuthenticated, currentView, + selectedCategory, theme, searchResults, - repositories + repositories, + setSelectedCategory, } = useAppStore(); - const [selectedCategory, setSelectedCategory] = useState('all'); - // 自动检查更新 useAutoUpdateCheck(); diff --git a/src/components/RepositoryList.tsx b/src/components/RepositoryList.tsx index 37df7c7..22d4e12 100644 --- a/src/components/RepositoryList.tsx +++ b/src/components/RepositoryList.tsx @@ -34,6 +34,7 @@ export const RepositoryList: React.FC = ({ const [showDropdown, setShowDropdown] = useState(false); const [isPaused, setIsPaused] = useState(false); const [disableCardAnimations, setDisableCardAnimations] = useState(false); + const previousCategoryRef = useRef(selectedCategory); const savedScrollYRef = useRef(null); const restoreScrollFrameRef = useRef(null); @@ -118,6 +119,13 @@ export const RepositoryList: React.FC = ({ setVisibleCount(LOAD_BATCH); }, [filterResetKey]); + useEffect(() => { + if (previousCategoryRef.current !== selectedCategory) { + window.scrollTo({ top: 0, behavior: 'auto' }); + previousCategoryRef.current = selectedCategory; + } + }, [selectedCategory]); + // Clamp visible count when result set becomes smaller, but do not collapse // back to the initial batch during backend sync refreshes. useEffect(() => { diff --git a/src/store/useAppStore.ts b/src/store/useAppStore.ts index 187d1d1..9a41d2f 100644 --- a/src/store/useAppStore.ts +++ b/src/store/useAppStore.ts @@ -71,6 +71,7 @@ interface AppActions { // UI actions setTheme: (theme: 'light' | 'dark') => void; setCurrentView: (view: 'repositories' | 'releases' | 'settings') => void; + setSelectedCategory: (category: string) => void; setLanguage: (language: 'zh' | 'en') => void; // Update actions @@ -112,6 +113,8 @@ type PersistedAppState = Partial< | 'customCategories' | 'assetFilters' | 'theme' + | 'currentView' + | 'selectedCategory' | 'language' | 'searchFilters' > @@ -276,6 +279,7 @@ export const useAppStore = create()( assetFilters: [], theme: 'light', currentView: 'repositories', + selectedCategory: 'all', language: 'zh', updateNotification: null, analysisProgress: { current: 0, total: 0 }, @@ -429,6 +433,7 @@ export const useAppStore = create()( // UI actions setTheme: (theme) => set({ theme }), setCurrentView: (currentView) => set({ currentView }), + setSelectedCategory: (selectedCategory) => set({ selectedCategory }), setLanguage: (language) => set({ language }), // Update actions @@ -476,6 +481,8 @@ export const useAppStore = create()( // 持久化UI设置 theme: state.theme, + currentView: state.currentView, + selectedCategory: state.selectedCategory, language: state.language, // backendApiSecret: 保留在内存中,不持久化(安全考虑) diff --git a/src/types/index.ts b/src/types/index.ts index 38fb887..057b191 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -155,6 +155,7 @@ export interface AppState { // UI theme: 'light' | 'dark'; currentView: 'repositories' | 'releases' | 'settings'; + selectedCategory: string; language: 'zh' | 'en'; // Update From 87c59e7148e1cec7a04056d0b21f7dddb286d03d Mon Sep 17 00:00:00 2001 From: Clawd Bot Date: Mon, 16 Mar 2026 21:43:43 +0800 Subject: [PATCH 5/5] fix: avoid redundant scroll restore after sync --- src/components/RepositoryList.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/RepositoryList.tsx b/src/components/RepositoryList.tsx index 22d4e12..8283a23 100644 --- a/src/components/RepositoryList.tsx +++ b/src/components/RepositoryList.tsx @@ -174,6 +174,10 @@ export const RepositoryList: React.FC = ({ const targetScrollY = savedScrollYRef.current; if (targetScrollY === null) return; + if (window.scrollY === targetScrollY) { + savedScrollYRef.current = null; + return; + } restoreScrollFrameRef.current = window.requestAnimationFrame(() => { restoreScrollFrameRef.current = window.requestAnimationFrame(() => {