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
2 changes: 1 addition & 1 deletion src/components/CodeSearchView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe('CodeSearchView', () => {

it('renders the idle empty state without fetching', () => {
render(<CodeSearchView />);
expect(screen.getByText('高级搜索')).toBeInTheDocument();
expect(screen.getByText('代码搜索')).toBeInTheDocument();
expect(mockedSearch).not.toHaveBeenCalled();
});

Expand Down
10 changes: 5 additions & 5 deletions src/components/CodeSearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export const CodeSearchView: React.FC = () => {
const translate = tRef.current;
setError(
isGrepRateLimitError(err)
? translate('触发 grep.app 限流(429),请稍后重试', 'grep.app rate limited (429), please retry later')
? translate('触发代码搜索限流(429),请稍后重试', 'Code search rate limited (429), please retry later')
: err instanceof Error
? err.message
: translate('搜索失败,请重试', 'Search failed, please retry')
Expand Down Expand Up @@ -426,7 +426,7 @@ export const CodeSearchView: React.FC = () => {
{loading && !result && (
<div className="flex flex-col items-center justify-center gap-3 py-14">
<Loader2 className="h-7 w-7 animate-spin text-primary" />
<p className="text-sm text-muted-foreground">{t('正在搜索 grep.app…', 'Searching grep.app…')}</p>
<p className="text-sm text-muted-foreground">{t('正在搜索代码…', 'Searching code…')}</p>
</div>
)}
{error && (
Expand All @@ -441,11 +441,11 @@ export const CodeSearchView: React.FC = () => {
{!loading && !error && !result && (
<div className="flex flex-col items-center justify-center gap-3 py-14 text-center">
<Search className="h-8 w-8 text-muted-foreground/50" />
<p className="font-medium text-muted-foreground">{t('高级搜索', 'Code Search')}</p>
<p className="font-medium text-muted-foreground">{t('代码搜索', 'Code Search')}</p>
<p className="max-w-sm text-sm text-muted-foreground">
{t(
'基于 grep.app 的公开仓库代码检索,支持模糊 / 全词 / 正则与仓库·路径·语言过滤。',
'Public code search via grep.app with fuzzy / whole-word / regexp and repo·path·language facets.'
'公开仓库代码全文检索,支持模糊 / 全词 / 正则与仓库·路径·语言过滤。',
'Full-text code search across public repos with fuzzy / whole-word / regexp and repo·path·language facets.'
)}
</p>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/components/DiscoverySidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { RefreshCw, Loader2, TrendingUp, Rocket, Crown, Tag, Search } from 'lucide-react';
import { RefreshCw, Loader2, TrendingUp, Rocket, Crown, Tag, Search, Newspaper } from 'lucide-react';
import type { DiscoveryChannel, DiscoveryChannelId, DiscoveryChannelIcon } from '../types';
import { Button } from './ui/button';

Expand All @@ -8,6 +8,7 @@ const discoveryChannelIconMap: Record<DiscoveryChannelIcon, React.ComponentType<
rocket: Rocket,
star: Crown,
tag: Tag,
weekly: Newspaper,
search: Search,
};

Expand Down
104 changes: 94 additions & 10 deletions src/components/DiscoveryView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
ChevronDown,
Globe,
X,
Calendar
Calendar,
Newspaper
} from 'lucide-react';
import { SiAndroid, SiApple, SiLinux } from '@icons-pack/react-simple-icons';
import { SiWindows } from './SiWindows';
Expand Down Expand Up @@ -43,6 +44,7 @@ const discoveryChannelIconMap: Record<DiscoveryChannelIcon, React.ReactNode> = {
rocket: <Rocket className="w-4 h-4 text-muted-foreground dark:text-muted-foreground" />,
star: <Crown className="w-4 h-4 text-muted-foreground dark:text-muted-foreground" />,
tag: <Tag className="w-4 h-4 text-muted-foreground dark:text-muted-foreground" />,
weekly: <Newspaper className="w-4 h-4 text-muted-foreground dark:text-muted-foreground" />,
search: <Search className="w-4 h-4 text-muted-foreground dark:text-muted-foreground" />,
};

Expand All @@ -67,6 +69,11 @@ const discoveryChannelStyleMap: Record<DiscoveryChannelIcon, { gradient: string;
shadow: 'shadow-subtle',
largeIcon: <Tag className="w-9 h-9 text-muted-foreground dark:text-foreground" />,
},
weekly: {
gradient: 'from-muted to-muted/60 dark:from-muted/40 dark:to-muted/20',
shadow: 'shadow-subtle',
largeIcon: <Newspaper className="w-9 h-9 text-muted-foreground dark:text-foreground" />,
},
search: {
gradient: 'from-muted to-muted/60 dark:from-muted/40 dark:to-muted/20',
shadow: 'shadow-subtle',
Expand Down Expand Up @@ -442,6 +449,9 @@ export const DiscoveryView: React.FC = React.memo(() => {
discoveryTotalCount,
trendingTimeRange,
setTrendingTimeRange,
weeklyOnlyCollected,
setWeeklyOnlyCollected,
weeklySyncStatus,
t,
isAnalyzing,
refreshChannel,
Expand Down Expand Up @@ -489,6 +499,12 @@ export const DiscoveryView: React.FC = React.memo(() => {
const currentChannelIcon = currentChannel?.icon || 'trending';
const currentChannelStyle = discoveryChannelStyleMap[currentChannelIcon] || discoveryChannelStyleMap.trending;
const currentChannelIconNode = discoveryChannelIconMap[currentChannelIcon] || discoveryChannelIconMap.trending;
// 周刊同步/补全进度文案(工具栏与空态加载两处共用)
const weeklyStatusText = weeklySyncStatus
? (weeklySyncStatus.phase === 'syncing'
? t(`正在同步周刊投稿… 已扫描 ${weeklySyncStatus.current} 条`, `Syncing weekly submissions… ${weeklySyncStatus.current} scanned`)
: t(`补全仓库详情… ${weeklySyncStatus.current}/${weeklySyncStatus.total}`, `Fetching repo details… ${weeklySyncStatus.current}/${weeklySyncStatus.total}`))
: null;



Expand All @@ -514,6 +530,17 @@ export const DiscoveryView: React.FC = React.memo(() => {
}
}, [trendingTimeRange, selectedDiscoveryChannel, refreshChannel]);

// 周刊收录过滤器切换时重建列表:只在偏好值真正变化时触发(挂载/切频道不触发,
// 避免与空频道自动加载 effect 双重调用导致增量同步被中止重启)
const prevWeeklyOnlyCollectedRef = useRef(weeklyOnlyCollected);
useEffect(() => {
if (prevWeeklyOnlyCollectedRef.current === weeklyOnlyCollected) return;
prevWeeklyOnlyCollectedRef.current = weeklyOnlyCollected;
if (selectedDiscoveryChannel === 'weekly') {
refreshChannel('weekly', 1, false);
}
}, [weeklyOnlyCollected, selectedDiscoveryChannel, refreshChannel]);

// 主题改变时刷新数据
useEffect(() => {
if (selectedDiscoveryChannel !== 'topic' || !githubToken) return;
Expand Down Expand Up @@ -747,16 +774,40 @@ export const DiscoveryView: React.FC = React.memo(() => {
</SelectContent>
</Select>
)}
{selectedDiscoveryChannel === 'weekly' && (
<button
type="button"
onClick={() => setWeeklyOnlyCollected(!weeklyOnlyCollected)}
aria-pressed={weeklyOnlyCollected}
className={`flex items-center gap-1.5 h-9 px-3 rounded-lg text-sm font-medium border transition-colors ${
weeklyOnlyCollected
? 'bg-primary/10 text-primary border-primary/30 dark:text-primary'
: 'bg-muted/50 text-muted-foreground border-transparent hover:bg-accent hover:text-accent-foreground'
}`}
title={t('仅显示已收录进周刊的投稿', 'Only show submissions included in the weekly issue')}
>
<Newspaper className="w-4 h-4" />
{t('周刊收录', 'In Weekly')}
</button>
)}
{selectedDiscoveryChannel === 'weekly' && weeklyStatusText && (
<span className="flex items-center gap-1.5 text-xs text-muted-foreground dark:text-muted-foreground" aria-live="polite">
<Loader2 className="w-3.5 h-3.5 animate-spin" />
{weeklyStatusText}
</span>
)}
<div className="flex items-center gap-1.5 flex-wrap">
<PlatformFilter
platform={discoveryPlatform}
onPlatformChange={setDiscoveryPlatform}
{selectedDiscoveryChannel !== 'weekly' && (
<PlatformFilter
platform={discoveryPlatform}
onPlatformChange={setDiscoveryPlatform}
language={language}
/>
)}
<SortAlgorithmTooltip
channelId={selectedDiscoveryChannel}
language={language}
/>
<SortAlgorithmTooltip
channelId={selectedDiscoveryChannel}
language={language}
/>
{isAnalyzingThisChannel ? (
<div className="flex items-center gap-1">
<div className="relative">
Expand Down Expand Up @@ -907,7 +958,9 @@ export const DiscoveryView: React.FC = React.memo(() => {
{t('正在获取数据…', 'Fetching data…')}
</p>
<p className="text-xs text-muted-foreground dark:text-muted-foreground">
{t('GitHub API 响应中', 'Waiting for GitHub API response')}
{selectedDiscoveryChannel === 'weekly' && weeklyStatusText
? weeklyStatusText
: t('GitHub API 响应中', 'Waiting for GitHub API response')}
</p>
</div>
</div>
Expand All @@ -928,13 +981,44 @@ export const DiscoveryView: React.FC = React.memo(() => {
)}
<div className="space-y-2 max-w-xs">
<p className="text-muted-foreground dark:text-muted-foreground font-medium text-base">
{t('简单搜索', 'Simple Search')}
{t('仓库搜索', 'Repo Search')}
</p>
<p className="text-sm text-muted-foreground dark:text-muted-foreground leading-relaxed">
{t('输入关键字搜索 GitHub 仓库', 'Enter keywords to search GitHub repositories')}
</p>
</div>
</>
) : selectedDiscoveryChannel === 'weekly' ? (
<>
{isDesktopSafeMode ? (
<div className="w-16 h-16 rounded-2xl bg-muted dark:bg-card flex items-center justify-center text-muted-foreground dark:text-muted-foreground border border-border dark:border-border">
{currentChannelIconNode}
</div>
) : (
<div className={`w-20 h-20 rounded-3xl bg-gradient-to-br ${currentChannelStyle.gradient} flex items-center justify-center shadow-md ${currentChannelStyle.shadow}`}>
{currentChannelStyle.largeIcon}
</div>
)}
<div className="space-y-2 max-w-xs">
<p className="text-muted-foreground dark:text-muted-foreground font-medium text-base">
{t('阮一峰周刊', 'Ruanyifeng Weekly')}
</p>
<p className="text-sm text-muted-foreground dark:text-muted-foreground leading-relaxed">
{t('同步科技爱好者周刊的开源项目投稿,加载更多时自动获取更早的投稿', 'Sync open-source submissions from the weekly. Earlier submissions are fetched as you load more')}
</p>
</div>
<Button
variant="default"
onClick={() => refreshChannel('weekly', 1, false)}
disabled={currentIsLoading}
className={isDesktopSafeMode
? 'flex items-center gap-2 rounded-lg bg-primary px-6 py-2.5 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90'
: 'flex items-center gap-2 rounded-xl bg-primary px-6 py-2.5 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90'}
>
<RefreshCw className="w-4 h-4" />
{t('开始同步', 'Start Sync')}
</Button>
</>
) : (
<>
{isDesktopSafeMode ? (
Expand Down
13 changes: 11 additions & 2 deletions src/components/SortAlgorithmTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,19 @@ export const SortAlgorithmTooltip: React.FC<SortAlgorithmTooltipProps> = ({ chan
'【Features】\n• Custom keyword search\n• Sort options: Best match, Most stars, Most forks\n• Language and platform filters\n\n【Best for】\nPrecise search for specific projects or tech stack related repos.'
),
};
case 'weekly':
return {
title: t('阮一峰周刊', 'Ruanyifeng Weekly'),
highlight: t('📰 科技爱好者周刊开源投稿精选', '📰 Open-source picks from the weekly'),
description: t(
'【特点】\n• 来源:ruanyf/weekly 的开源投稿 issue\n• 自动提取正文中的仓库链接\n• 排序方式:按投稿时间倒序\n• 可过滤已被周刊收录的条目\n\n【适合场景】\n浏览经人工筛选视角推荐的开源项目,发现周刊读者关注的优质仓库。',
'【Features】\n• Source: open-source submission issues of ruanyf/weekly\n• Repo links extracted from issue bodies\n• Sort by: submission time descending\n• Filter by weekly-collected items\n\n【Best for】\nBrowsing open-source projects recommended through the weekly editorial lens.'
),
};
case 'code-search':
return {
title: t('高级搜索', 'Code Search'),
highlight: t('🔍 grep.app 代码全文检索', '🔍 Full-text code search via grep.app'),
title: t('代码搜索', 'Code Search'),
highlight: t('🔍 公开仓库代码全文检索', '🔍 Full-text code search across public repos'),
description: t(
'【特点】\n• 模糊 / 全词 / 正则三种匹配,可叠加区分大小写\n• 仓库 / 路径 / 语言过滤器动态筛选\n• 支持只看我收藏的仓库\n\n【适合场景】\n在公开仓库代码中定位用法、配置与示例。',
'【Features】\n• Fuzzy / whole-word / regexp modes, optional case sensitivity\n• Dynamic repo / path / language facets\n• Optional starred-only filter\n\n【Best for】\nFinding usages, configs and examples across public code.'
Expand Down
Loading