Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
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