Skip to content
Merged
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export const Header: React.FC = () => {
if (existing) {
return {
...newRepo,
has_fetched_releases: existing.has_fetched_releases,
last_release_fetch_time: existing.last_release_fetch_time,
ai_summary: existing.ai_summary,
ai_tags: existing.ai_tags,
ai_platforms: existing.ai_platforms,
Expand All @@ -99,10 +101,8 @@ export const Header: React.FC = () => {

setRepositories(mergedRepositories);

// 3. 获取Release信息
console.log('Fetching releases...');
const releases = await githubApi.getMultipleRepositoryReleases(mergedRepositories.slice(0, 20));
setReleases(releases);
// Note: Release fetching is now handled by the Refresh button in Release Timeline
// Header sync only syncs the starred repos list

setLastSync(new Date().toISOString());
console.log('Sync completed successfully');
Expand Down Expand Up @@ -367,7 +367,7 @@ export const Header: React.FC = () => {
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')}
title={t('同步星标仓库列表(不包含Release)', 'Sync starred repos list (excludes Release)')}
>
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
</button>
Expand Down
118 changes: 81 additions & 37 deletions src/components/ReleaseTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const ReleaseTimeline: React.FC = () => {
setReleaseSearchQuery,
toggleReleaseExpandedRepository,
setReleaseIsRefreshing,
includePreRelease,
setIncludePreRelease,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} = useAppStore();

const { toast, confirm } = useDialog();
Expand Down Expand Up @@ -179,9 +181,12 @@ export const ReleaseTimeline: React.FC = () => {
return links;
}, []);

const subscribedReleases = useMemo(() =>
releases.filter(release => releaseSubscriptions.has(release.repository.id)),
[releases, releaseSubscriptions]
const subscribedReleases = useMemo(() =>
releases.filter(release =>
releaseSubscriptions.has(release.repository.id) &&
(includePreRelease || !release.prerelease)
),
[releases, releaseSubscriptions, includePreRelease]
);

// 预计算每个 release 的下载链接和过滤后的链接
Expand Down Expand Up @@ -301,56 +306,58 @@ export const ReleaseTimeline: React.FC = () => {
setReleaseIsRefreshing(true);
try {
const githubApi = new GitHubApiService(githubToken);
// Only fetch releases for repos that are subscribed to releases
const subscribedRepos = repositories.filter(repo => releaseSubscriptions.has(repo.id));

if (subscribedRepos.length === 0) {
toast(language === 'zh' ? '没有订阅的仓库。' : 'No subscribed repositories.', 'error');
return;
}

const allNewReleases: Release[] = [];

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;
// Use the new getMultipleRepositoryReleases with options
const { releases: newReleases, failedRepos } = await githubApi.getMultipleRepositoryReleases(
subscribedRepos,
{ includePreRelease }
);

// Update repository sync metadata only for repos that succeeded
const now = new Date().toISOString();
const failedRepoIds = new Set(failedRepos.map(repo => repo.repoId));
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);
if (failedRepoIds.has(repo.id)) {
continue;
}

repoReleases.forEach(release => {
release.repository.id = repo.id;
updateRepository({
...repo,
has_fetched_releases: true,
last_release_fetch_time: now,
});

allNewReleases.push(...repoReleases);

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

// Filter out existing releases and add new ones
const existingIds = new Set(useAppStore.getState().releases.map(r => r.id));
const actuallyNewCount = allNewReleases.filter(r => !existingIds.has(r.id)).length;
const actuallyNewReleases = newReleases.filter(r => !existingIds.has(r.id));
const actuallyNewCount = actuallyNewReleases.length;

if (allNewReleases.length > 0) {
addReleases(allNewReleases);
if (actuallyNewReleases.length > 0) {
addReleases(actuallyNewReleases);
}

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

const message = language === 'zh'
? `刷新完成!发现 ${actuallyNewCount} 个新Release。`
: `Refresh completed! Found ${actuallyNewCount} new releases.`;
// Build success message with failed repos info
let message: string;
if (failedRepos.length > 0) {
message = language === 'zh'
? `刷新完成!发现 ${actuallyNewCount} 个新Release,${failedRepos.length} 个仓库刷新失败。`
: `Refresh completed! Found ${actuallyNewCount} new releases, ${failedRepos.length} repos failed.`;
} else {
message = language === 'zh'
? `刷新完成!发现 ${actuallyNewCount} 个新Release。`
: `Refresh completed! Found ${actuallyNewCount} new releases.`;
}

toast(message, 'success');
toast(message, actuallyNewCount > 0 ? 'success' : 'info');
} catch (error) {
console.error('Refresh failed:', error);
const errorMessage = language === 'zh'
Expand Down Expand Up @@ -527,19 +534,38 @@ export const ReleaseTimeline: React.FC = () => {
}
</p>

{/* 刷新按钮 - 在有订阅仓库时显示 */}
{/* Pre-release toggle + Refresh button */}
{subscribedRepoCount > 0 && (
<div className="mb-6">
<div className="mb-6 flex flex-col items-center gap-3">
{/* Pre-release toggle */}
<label className="flex items-center gap-2 cursor-pointer select-none">
<button
type="button"
role="switch"
aria-checked={includePreRelease}
onClick={() => setIncludePreRelease(!includePreRelease)}
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${includePreRelease ? 'bg-brand-indigo' : 'bg-gray-300 dark:bg-gray-600'}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${includePreRelease ? 'translate-x-[18px]' : 'translate-x-[2px]'}`}
/>
</button>
<span className="text-sm text-gray-600 dark:text-text-secondary">
{t('包含 Pre-release', 'Include Pre-release')}
</span>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</label>

{/* Refresh button */}
<button
onClick={handleRefresh}
disabled={releaseIsRefreshing}
className="flex items-center space-x-2 px-6 py-3 bg-brand-indigo text-white rounded-lg hover:bg-brand-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed mx-auto"
className="flex items-center space-x-2 px-6 py-3 bg-brand-indigo text-white rounded-lg hover:bg-brand-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<RefreshCw className={`w-5 h-5 ${releaseIsRefreshing ? 'animate-spin' : ''}`} />
<span>{releaseIsRefreshing ? t('刷新中...', 'Refreshing...') : t('刷新Release', 'Refresh Releases')}</span>
</button>
{lastRefreshTime && (
<p className="text-sm text-gray-500 dark:text-text-tertiary mt-2">
<p className="text-sm text-gray-500 dark:text-text-tertiary">
{t('上次刷新:', 'Last refresh:')} {formatDistanceToNow(new Date(lastRefreshTime), { addSuffix: true })}
</p>
)}
Expand Down Expand Up @@ -598,6 +624,24 @@ export const ReleaseTimeline: React.FC = () => {
</span>
)}

{/* Pre-release toggle */}
<label className="flex items-center gap-1.5 cursor-pointer select-none">
<button
type="button"
role="switch"
aria-checked={includePreRelease}
onClick={() => setIncludePreRelease(!includePreRelease)}
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${includePreRelease ? 'bg-brand-indigo' : 'bg-gray-300 dark:bg-gray-600'}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${includePreRelease ? 'translate-x-[18px]' : 'translate-x-[2px]'}`}
/>
</button>
<span className="text-xs text-gray-600 dark:text-text-secondary hidden sm:inline">
{t('Pre', 'Pre')}
</span>
</label>

{/* Refresh Button */}
<button
onClick={handleRefresh}
Expand Down
Loading
Loading