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
25 changes: 25 additions & 0 deletions server/src/routes/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,32 @@ router.put('/api/repositories', (req, res) => {
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);

const deleteAllReleases = db.prepare('DELETE FROM releases');
const deleteAllRepositories = db.prepare('DELETE FROM repositories');
const deleteReleasesNotIn = (placeholders: string) =>
db.prepare(`DELETE FROM releases WHERE repo_id NOT IN (${placeholders})`);
const deleteRepositoriesNotIn = (placeholders: string) =>
db.prepare(`DELETE FROM repositories WHERE id NOT IN (${placeholders})`);

const upsert = db.transaction(() => {
const isFullSync = Boolean(req.body?.isFullSync);

if (isFullSync) {
const repoIds = repositories
.map((repo) => repo.id)
.filter((id): id is number => typeof id === 'number');

if (repoIds.length === 0) {
deleteAllReleases.run();
deleteAllRepositories.run();
return 0;
}

const placeholders = repoIds.map(() => '?').join(', ');
deleteReleasesNotIn(placeholders).run(...repoIds);
deleteRepositoriesNotIn(placeholders).run(...repoIds);
}

let count = 0;
for (const repo of repositories) {
const owner = repo.owner as { login?: string; avatar_url?: string } | undefined;
Expand Down
6 changes: 3 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function App() {
switch (currentView) {
case 'repositories':
return (
<div className="flex space-x-6">
<div className="flex flex-col gap-4 lg:flex-row lg:gap-6">
<CategorySidebar
repositories={repositories}
selectedCategory={selectedCategory}
Expand Down Expand Up @@ -102,11 +102,11 @@ function App() {
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<UpdateNotificationBanner />
<Header />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<main className="max-w-7xl mx-auto px-3 sm:px-6 lg:px-8 py-4 sm:py-8">
{renderCurrentView()}
</main>
</div>
);
}

export default App;
export default App;
10 changes: 5 additions & 5 deletions src/components/CategorySidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const CategorySidebar: React.FC<CategorySidebarProps> = ({

return (
<>
<div className="w-64 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-4 max-h-[calc(100vh-8rem)] sticky top-24 overflow-y-auto">
<div className="w-full lg:w-64 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-3 sm:p-4 lg:max-h-[calc(100vh-8rem)] lg:sticky lg:top-24 overflow-hidden lg:overflow-y-auto">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{t('应用分类', 'Categories')}
Expand All @@ -116,16 +116,16 @@ export const CategorySidebar: React.FC<CategorySidebarProps> = ({
</button>
</div>

<div className="space-y-1">
<div className="flex gap-2 overflow-x-auto pb-1 lg:block lg:space-y-1 lg:overflow-visible">
{allCategories.map(category => {
const count = getCategoryCount(category);
const isSelected = selectedCategory === category.id;

return (
<div key={category.id} className="group">
<div key={category.id} className="group shrink-0 lg:shrink">
<button
onClick={() => onCategorySelect(category.id)}
className={`w-full flex items-center justify-between px-3 py-2.5 rounded-lg text-left transition-colors ${
className={`flex min-w-[140px] items-center justify-between px-3 py-2.5 rounded-lg text-left transition-colors lg:w-full ${
isSelected
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
Expand Down Expand Up @@ -177,4 +177,4 @@ export const CategorySidebar: React.FC<CategorySidebarProps> = ({
/>
</>
);
};
};
84 changes: 63 additions & 21 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,27 +106,69 @@ export const Header: React.FC = () => {
return (
<header className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-50 hd-drag">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex flex-col gap-3 py-3 sm:h-16 sm:flex-row sm:items-center sm:justify-between sm:py-0">
{/* Logo and Title */}
<div className="flex items-center space-x-3">
<div className="flex items-center justify-center w-10 h-10 rounded-lg overflow-hidden">
<img
src="./icon.png"
alt="GitHub Stars Manager"
className="w-10 h-10 object-cover"
/>
</div>
<div>
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
GitHub Stars Manager
</h1>
<p className="text-sm text-gray-500 dark:text-gray-400">
AI-powered repository management
</p>
<div className="flex items-center justify-between gap-3 sm:justify-start">
<div className="flex min-w-0 items-center space-x-3">
<div className="flex items-center justify-center w-10 h-10 rounded-lg overflow-hidden">
<img
src="./icon.png"
alt="GitHub Stars Manager"
className="w-10 h-10 object-cover"
/>
</div>
<div className="min-w-0">
<h1 className="truncate text-xl font-bold text-gray-900 dark:text-white">
GitHub Stars Manager
</h1>
<p className="truncate text-sm text-gray-500 dark:text-gray-400">
AI-powered repository management
</p>
</div>
</div>
<button
onClick={handleSync}
disabled={isLoading}
className="sm:hidden inline-flex items-center justify-center rounded-lg border border-gray-200 p-2 text-gray-600 transition-colors hover:bg-gray-100 disabled:opacity-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
title={t('同步仓库', 'Sync repositories')}
>
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
</button>
</div>

{/* Navigation */}
<nav className="grid w-full grid-cols-3 gap-2 sm:hidden hd-btns">
<button
onClick={() => setCurrentView('repositories')}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
currentView === 'repositories'
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
>
{t('仓库', 'Repos')}
</button>
<button
onClick={() => setCurrentView('releases')}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
currentView === 'releases'
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
>
{t('发布', 'Releases')}
</button>
<button
onClick={() => setCurrentView('settings')}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
currentView === 'settings'
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
>
{t('设置', 'Settings')}
</button>
</nav>
<nav className="hidden md:flex items-center space-x-1 hd-btns">
<button
onClick={() => setCurrentView('repositories')}
Expand Down Expand Up @@ -164,7 +206,7 @@ export const Header: React.FC = () => {
</nav>

{/* User Actions */}
<div className="flex items-center space-x-3 hd-btns">
<div className="flex items-center justify-between gap-3 sm:justify-end hd-btns">
{/* Sync Status */}
<div className="hidden sm:flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400">
<span>{t('上次同步:', 'Last sync:')} {formatLastSync(lastSync)}</span>
Expand Down Expand Up @@ -193,14 +235,14 @@ export const Header: React.FC = () => {

{/* User Profile */}
{user && (
<div className="flex items-center space-x-3">
<div className="flex min-w-0 items-center space-x-3">
<img
src={user.avatar_url}
alt={user.name || user.login}
className="w-8 h-8 rounded-full"
/>
<div className="hidden sm:block">
<p className="text-sm font-medium text-gray-900 dark:text-white">
<div className="min-w-0">
<p className="truncate text-sm font-medium text-gray-900 dark:text-white">
{user.name || user.login}
</p>
</div>
Expand All @@ -218,4 +260,4 @@ export const Header: React.FC = () => {
</div>
</header>
);
};
};
18 changes: 9 additions & 9 deletions src/components/ReleaseTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ export const ReleaseTimeline: React.FC = () => {
return (
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<div className="mb-6 sm:mb-8">
<div className="flex flex-col gap-4 mb-4 lg:flex-row lg:items-start lg:justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
{t('Release时间线', 'Release Timeline')}
Expand All @@ -356,7 +356,7 @@ export const ReleaseTimeline: React.FC = () => {
{t(`来自您的 ${releaseSubscriptions.size} 个订阅仓库的最新Release`, `Latest releases from your ${releaseSubscriptions.size} subscribed repositories`)}
</p>
</div>
<div className="flex items-center space-x-3">
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
{/* View Mode Toggle */}
<div className="flex items-center space-x-2">
<button
Expand Down Expand Up @@ -385,7 +385,7 @@ export const ReleaseTimeline: React.FC = () => {

{/* Last Refresh Time */}
{lastRefreshTime && (
<span className="text-sm text-gray-500 dark:text-gray-400">
<span className="w-full text-sm text-gray-500 dark:text-gray-400 lg:w-auto">
{t('上次刷新:', 'Last refresh:')} {formatDistanceToNow(new Date(lastRefreshTime), { addSuffix: true })}
</span>
)}
Expand Down Expand Up @@ -452,8 +452,8 @@ export const ReleaseTimeline: React.FC = () => {
</div>

{/* Results Info and Pagination Controls */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-4">
<div className="flex flex-col gap-3 mb-6 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-wrap items-center gap-2 sm:gap-4">
<span className="text-sm text-gray-600 dark:text-gray-400">
{t(
`显示 ${startIndex + 1}-${Math.min(startIndex + itemsPerPage, filteredReleases.length)} 共 ${filteredReleases.length} 个Release`,
Expand All @@ -467,7 +467,7 @@ export const ReleaseTimeline: React.FC = () => {
)}
</div>

<div className="flex items-center space-x-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
{/* Items per page selector */}
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-600 dark:text-gray-400">{t('每页:', 'Per page:')}</span>
Expand All @@ -488,7 +488,7 @@ export const ReleaseTimeline: React.FC = () => {

{/* Pagination */}
{totalPages > 1 && (
<div className="flex items-center space-x-1">
<div className="flex items-center space-x-1 overflow-x-auto pb-1">
<button
onClick={() => handlePageChange(1)}
disabled={currentPage === 1}
Expand Down Expand Up @@ -953,4 +953,4 @@ export const ReleaseTimeline: React.FC = () => {
)}
</div>
);
};
};
6 changes: 4 additions & 2 deletions src/components/RepositoryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Repository } from '../types';
import { useAppStore, getAllCategories } from '../store/useAppStore';
import { GitHubApiService } from '../services/githubApi';
import { AIService } from '../services/aiService';
import { forceSyncToBackend } from '../services/autoSync';
import { formatDistanceToNow } from 'date-fns';
import { RepositoryEditModal } from './RepositoryEditModal';

Expand Down Expand Up @@ -333,6 +334,7 @@ export const RepositoryCard: React.FC<RepositoryCardProps> = ({
const [owner, repo] = repository.full_name.split('/');
await githubApi.unstarRepository(owner, repo);
deleteRepository(repository.id);
await forceSyncToBackend();
const successMessage = language === 'zh'
? '已成功取消 Star'
: 'Successfully unstarred';
Expand All @@ -349,7 +351,7 @@ export const RepositoryCard: React.FC<RepositoryCardProps> = ({
};

return (
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6 hover:shadow-lg transition-all duration-200 hover:border-blue-300 dark:hover:border-blue-600 animate-slide-up flex flex-col h-full">
<div className="repository-card bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6 hover:shadow-lg transition-all duration-200 hover:border-blue-300 dark:hover:border-blue-600 flex flex-col h-full">
{/* Header - Repository Info */}
<div className="flex items-center space-x-3 mb-3">
<img
Expand Down Expand Up @@ -603,4 +605,4 @@ export const RepositoryCard: React.FC<RepositoryCardProps> = ({
/>
</div>
);
};
};
Loading
Loading