Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 12 additions & 19 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,7 @@ export const Header: React.FC = () => {
});

setRepositories(mergedRepositories);

// 3. 获取Release信息
console.log('Fetching releases...');
const releases = await githubApi.getMultipleRepositoryReleases(mergedRepositories.slice(0, 20));
setReleases(releases);

setLastSync(new Date().toISOString());
console.log('Sync completed successfully');

// 显示同步结果
const newRepoCount = newRepositories.length - repositories.length;
Expand Down Expand Up @@ -360,18 +353,18 @@ export const Header: React.FC = () => {

{/* User Actions */}
<div className="flex items-center gap-2 sm:gap-3 hd-btns lg:hd-btns">
{/* Sync Status */}
<div className="hidden sm:flex items-center space-x-2 text-sm text-gray-500 dark:text-text-tertiary">
<span>{t('上次同步:', 'Last sync:')} {formatLastSync(lastSync)}</span>
<button
onClick={handleSync}
disabled={isLoading}
className="p-1 rounded hover:bg-light-surface dark:hover:bg-white/5 transition-colors disabled:opacity-50"
title={t('同步仓库', 'Sync repositories')}
>
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
</button>
</div>
{/* Sync Status */}
<div className="hidden sm:flex items-center space-x-2 text-sm text-gray-500 dark:text-text-tertiary">
<span>{t('上次同步:', 'Last sync:')} {formatLastSync(lastSync)}</span>
<button
onClick={handleSync}
disabled={isLoading}
className="p-1 rounded hover:bg-light-surface dark:hover:bg-white/5 transition-colors disabled:opacity-50"
title={t('同步 Star 仓库列表(仅更新仓库信息,不含 Release)', 'Sync starred repositories (metadata only, excludes releases)')}
>
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
</button>
</div>

{/* Theme Toggle */}
<button
Expand Down
107 changes: 65 additions & 42 deletions src/components/ReleaseTimeline.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { Package, Bell, Search, X, RefreshCw, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, LayoutGrid, CalendarDays, ChevronDown } from 'lucide-react';
import { Package, Bell, Search, X, RefreshCw, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, LayoutGrid, CalendarDays, ChevronDown, HelpCircle, Plus } from 'lucide-react';
import { Release } from '../types';
import { useAppStore } from '../store/useAppStore';
import { GitHubApiService } from '../services/githubApi';
Expand Down Expand Up @@ -30,12 +30,14 @@ export const ReleaseTimeline: React.FC = () => {
releaseSearchQuery,
releaseExpandedRepositories,
releaseIsRefreshing,
includePreRelease,
setReleaseViewMode,
toggleReleaseSelectedFilter,
clearReleaseSelectedFilters,
setReleaseSearchQuery,
toggleReleaseExpandedRepository,
setReleaseIsRefreshing,
setIncludePreRelease,
} = useAppStore();

const { toast, confirm } = useDialog();
Expand Down Expand Up @@ -308,33 +310,24 @@ export const ReleaseTimeline: React.FC = () => {
return;
}

const allNewReleases: Release[] = [];
const refreshStartedAt = new Date().toISOString();

const latestReleaseTime = releases.length > 0
? releases.reduce((max, r) => Math.max(max, new Date(r.published_at).getTime()), 0)
: 0;
const sinceTimestamp = latestReleaseTime > 0 ? new Date(latestReleaseTime).toISOString() : undefined;
const { releases: fetchedReleases, failedRepos } =
await githubApi.getMultipleRepositoryReleases(subscribedRepos, 30);

for (const repo of subscribedRepos) {
const [owner, name] = repo.full_name.split('/');

const hasExistingReleases = releases.some(r => r.repository.id === repo.id);

let repoReleases: Release[];
if (!hasExistingReleases) {
repoReleases = await githubApi.getRepositoryReleases(owner, name, 1, 10);
} else {
repoReleases = await githubApi.getIncrementalRepositoryReleases(owner, name, sinceTimestamp, 10);
}

repoReleases.forEach(release => {
release.repository.id = repo.id;
const allNewReleases = includePreRelease?
fetchedReleases:
fetchedReleases.filter(r => !r.prelease);

Comment on lines +318 to +321

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the specific lines mentioned in the review comment
head -n 330 src/components/ReleaseTimeline.tsx | tail -n 20

Repository: AmintaCCCP/GithubStarsManager

Length of output: 722


🏁 Script executed:

# Also check the type definitions to understand the Release object structure
fd -t f "\.ts$|\.tsx$" | xargs grep -l "prelease\|prerelease" | head -10

Repository: AmintaCCCP/GithubStarsManager

Length of output: 257


🏁 Script executed:

# Check around lines 667-674 for the JSX issue mentioned
sed -n '660,680p' src/components/ReleaseTimeline.tsx

Repository: AmintaCCCP/GithubStarsManager

Length of output: 931


🏁 Script executed:

# Check the githubApi.ts file to see the return type of getMultipleRepositoryReleases
grep -A 20 "getMultipleRepositoryReleases" src/services/githubApi.ts | head -40

Repository: AmintaCCCP/GithubStarsManager

Length of output: 858


🏁 Script executed:

# Check type definitions for Release object
cat src/types/index.ts | grep -A 10 -B 2 "prerelease\|prelease"

Repository: AmintaCCCP/GithubStarsManager

Length of output: 291


🏁 Script executed:

# Also check server types since releases might be defined there
cat server/src/types/api.ts | grep -A 10 -B 2 "prerelease\|prelease"

Repository: AmintaCCCP/GithubStarsManager

Length of output: 332


Fix prerelease typo in filter—property name is misspelled.

Line 320 uses r.prelease instead of the correct property name r.prerelease. This causes the filter to fail (accessing undefined property) and will fail TypeScript type-checking.

🔧 Proposed fix
      const allNewReleases = includePreRelease?
        fetchedReleases:
-       fetchedReleases.filter(r => !r.prelease);
+       fetchedReleases.filter(r => !r.prerelease);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ReleaseTimeline.tsx` around lines 318 - 321, The filter in the
assignment to allNewReleases uses a misspelled property r.prelease; update the
predicate to check r.prerelease instead (when includePreRelease is false) so
fetchedReleases.filter(r => !r.prerelease) is used; this fixes the typo in the
variable allNewReleases and restores correct TypeScript typing for the
includePreRelease/fetchedReleases logic.

const failedRepoNames = new Set(failedRepos.map(r => r.full_name));
subscribedRepos.forEach(repo => {
if (failedRepoNames.has(repo.full_name)) return;
updateRepository({
...repo,
lastReleaseSyncTime: refreshStartedAt,
hasFetchedReleases: true,
});

allNewReleases.push(...repoReleases);

await new Promise(resolve => setTimeout(resolve, 200));
}
});

const existingIds = new Set(useAppStore.getState().releases.map(r => r.id));
const actuallyNewCount = allNewReleases.filter(r => !existingIds.has(r.id)).length;
Expand All @@ -343,14 +336,21 @@ export const ReleaseTimeline: React.FC = () => {
addReleases(allNewReleases);
}

const now = new Date().toISOString();
setLastRefreshTime(now);
setLastRefreshTime(refreshStartedAt);

const message = language === 'zh'
? `刷新完成!发现 ${actuallyNewCount} 个新Release。`
: `Refresh completed! Found ${actuallyNewCount} new releases.`;

toast(message, 'success');
// 汇总 toast
if (failedRepos.length > 0) {
const failedNames = failedRepos.map(r => r.full_name).join('、');
const message = language === 'zh'
? `刷新完成!新增 ${actuallyNewCount} 个Release,${failedRepos.length} 个仓库失败:${failedNames}`
: `Refresh completed! ${actuallyNewCount} new releases, ${failedRepos.length} failed: ${failedRepos.map(r => r.full_name).join(', ')}`;
toast(message, failedRepos.length > 0 ? 'warning' : 'success');
} else {
const message = language === 'zh'
? `刷新完成!发现 ${actuallyNewCount} 个新Release。`
: `Refresh completed! Found ${actuallyNewCount} new releases.`;
toast(message, 'success');
}
} catch (error) {
console.error('Refresh failed:', error);
const errorMessage = language === 'zh'
Expand Down Expand Up @@ -598,15 +598,31 @@ export const ReleaseTimeline: React.FC = () => {
</span>
)}

{/* Refresh Button */}
<button
onClick={handleRefresh}
disabled={releaseIsRefreshing}
className="flex items-center space-x-2 px-4 py-2 bg-brand-indigo text-white rounded-lg hover:bg-brand-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<RefreshCw className={`w-4 h-4 ${releaseIsRefreshing ? 'animate-spin' : ''}`} />
<span>{releaseIsRefreshing ? t('刷新中...', 'Refreshing...') : t('刷新', 'Refresh')}</span>
</button>
<div className="flex items-center space-x-2">
<button
onClick={handleRefresh}
disabled={releaseIsRefreshing}
className="flex items-center space-x-2 px-4 py-2 bg-brand-indigo text-white rounded-lg hover:bg-brand-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<RefreshCw className={`w-4 h-4 ${releaseIsRefreshing ? 'animate-spin' : ''}`} />
<span>{releaseIsRefreshing ? t('刷新中...', 'Refreshing...') : t('刷新', 'Refresh')}</span>
</button>
<label className="flex items-center gap-2 cursor-pointer select-none">
<input
type="checkbox"
checked={includePreRelease}
onChange={(e) => setIncludePreRelease(e.target.checked)}
className="w-4 h-4 rounded border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-brand-indigo focus:ring-2 focus:ring-brand-violet cursor-pointer"
/>
<span className="text-sm text-gray-700 dark:text-text-secondary">
{t('包含 Pre-release', 'Include Pre-release')}
</span>
</label>
<HelpCircle
className="w-4 h-4 text-gray-400 dark:text-text-quaternary cursor-help"
title={t('增量刷新已订阅仓库的 Release,仅获取上次同步后的新版本', 'Incremental refresh for subscribed repos (only new since last sync)')}
/>
</div>
</div>
</div>

Expand Down Expand Up @@ -640,12 +656,19 @@ export const ReleaseTimeline: React.FC = () => {

{/* Filters and View Toggle Row */}
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
<div className="flex-1">
<div className="flex items-center gap-3 flex-wrap">
<AssetFilterManager
selectedFilters={selectedFilters}
onFilterToggle={handleFilterToggle}
onClearFilters={handleClearFilters}
/>
<button
className="flex items-center gap-1 px-3 py-1.5 text-sm bg-light-surface dark:bg-white/[0.04] hover:bg-gray-200 dark:hover:bg-white/10 text-gray-700 dark:text-text-secondary rounded-lg transition-colors"
title={t('新建过滤器', 'New Filter')}
>
<Plus className="w-3.5 h-3.5" />
<span>{t('新建', 'New')}</span>
</button>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>

{/* View Mode Toggle Dropdown */}
Expand Down
Loading
Loading