This commit is contained in:
2025-11-07 19:44:51 +08:00
commit a0e157655f
6 changed files with 672 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
<div class="flex flex-col gap-2">
<p class="text-slate-900 dark:text-white text-4xl font-black leading-tight tracking-[-0.033em]">CodeZen</p>
<p class="text-slate-500 dark:text-slate-400 text-lg font-medium leading-tight">一个专注中文区的 GitHub 项目发现</p>
</div>
<div class="flex flex-wrap gap-3 pt-4 border-t border-slate-200 dark:border-slate-800 mt-6">
<a href="/?topic={{ current_topic|urlencode }}&sort={{ current_sort }}&q={{ search_query }}"
hx-get="/?topic={{ current_topic|urlencode }}&sort={{ current_sort }}&q={{ search_query }}"
hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true"
class="flex h-9 shrink-0 items-center justify-center gap-x-2 rounded-lg bg-slate-200 dark:bg-slate-800 px-4 hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors duration-200 no-underline {{ 'filter-button-active' if not current_lang }}">
<p class="text-slate-800 dark:text-white text-sm font-medium leading-normal">所有语言</p>
</a>
{% for lang in languages %}
<a href="/?lang={{ lang|urlencode }}&topic={{ current_topic|urlencode }}&sort={{ current_sort }}&q={{ search_query }}"
hx-get="/?lang={{ lang|urlencode }}&topic={{ current_topic|urlencode }}&sort={{ current_sort }}&q={{ search_query }}"
hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true"
class="flex h-9 shrink-0 items-center justify-center gap-x-2 rounded-lg bg-slate-200 dark:bg-slate-800 px-4 hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors duration-200 no-underline {{ 'filter-button-active' if current_lang == lang }}">
<p class="text-slate-800 dark:text-white text-sm font-medium leading-normal">{{ lang }}</p>
</a>
{% endfor %}
</div>
{% if popular_topics %}
<div class="flex flex-wrap gap-3 mt-4">
<a href="/?lang={{ current_lang|urlencode }}&sort={{ current_sort }}&q={{ search_query }}"
hx-get="/?lang={{ current_lang|urlencode }}&sort={{ current_sort }}&q={{ search_query }}"
hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true"
class="flex h-9 shrink-0 items-center justify-center gap-x-2 rounded-lg bg-slate-200 dark:bg-slate-800 px-4 hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors duration-200 no-underline {{ 'filter-button-active' if not current_topic }}">
<p class="text-slate-800 dark:text-white text-sm font-medium leading-normal">所有主题</p>
</a>
{% for topic in popular_topics %}
<a href="/?topic={{ topic|urlencode }}&lang={{ current_lang|urlencode }}&sort={{ current_sort }}&q={{ search_query }}"
hx-get="/?topic={{ topic|urlencode }}&lang={{ current_lang|urlencode }}&sort={{ current_sort }}&q={{ search_query }}"
hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true"
class="flex h-9 shrink-0 items-center justify-center gap-x-2 rounded-lg bg-slate-200 dark:bg-slate-800 px-4 hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors duration-200 no-underline {{ 'filter-button-active' if current_topic == topic }}">
<p class="text-slate-800 dark:text-white text-sm font-medium leading-normal">{{ topic }}</p>
</a>
{% endfor %}
</div>
{% endif %}
<div class="flex flex-wrap gap-3 mt-4">
<a href="/?sort=pushed_at&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-get="/?sort=pushed_at&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true"
class="flex h-9 shrink-0 items-center justify-center gap-x-2 rounded-lg bg-slate-200 dark:bg-slate-800 px-4 hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors duration-200 no-underline {{ 'filter-button-active' if current_sort == 'pushed_at' }}">
<p class="text-slate-800 dark:text-white text-sm font-medium leading-normal">按时间排序</p>
<div class="text-slate-600 dark:text-slate-400"><span class="material-symbols-outlined">expand_more</span></div>
</a>
<a href="/?sort=stars&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-get="/?sort=stars&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true"
class="flex h-9 shrink-0 items-center justify-center gap-x-2 rounded-lg bg-slate-200 dark:bg-slate-800 px-4 hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors duration-200 no-underline {{ 'filter-button-active' if current_sort == 'stars' }}">
<p class="text-slate-800 dark:text-white text-sm font-medium leading-normal">按热度排序</p>
<div class="text-slate-600 dark:text-slate-400"><span class="material-symbols-outlined">expand_more</span></div>
</a>
</div>
<div class="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] gap-6 mt-6">
{% for project in projects %}
<a href="{{ project['url'] }}" target="_blank" class="no-underline flex flex-col gap-4 p-5 rounded-xl bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 hover:shadow-lg hover:-translate-y-1 transition-all duration-300">
<div class="flex items-center gap-3">
{% if project['owner_avatar_url'] %}
<img src="{{ project['owner_avatar_url'] }}" alt="avatar" class="w-10 h-10 rounded-full bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700">
{% endif %}
<p class="text-slate-900 dark:text-white text-xl font-bold leading-normal truncate">{{ project['name'] }}</p>
</div>
<p class="text-slate-600 dark:text-slate-400 text-sm font-normal leading-normal min-h-[4rem] pt-4 border-t border-slate-200 dark:border-slate-700">{{ project['display_text'] }}</p>
{% if project['topics'] %}
<div classg="flex flex-wrap gap-2 -mt-2">
{% for topic in project['topics'][:5] %}
<span class="text-xs font-medium bg-blue-100 text-primary dark:bg-slate-800 dark:text-blue-300 px-2.5 py-0.5 rounded-full">{{ topic }}</span>
{% endfor %}
</div>
{% endif %}
<div class="flex items-center gap-4 text-slate-500 dark:text-slate-500 text-sm mt-auto pt-3 border-t border-slate-200 dark:border-slate-700">
<div class="flex items-center gap-1.5"><span class="material-symbols-outlined text-yellow-500" style="font-size: 16px;">star</span><span>{{ format_stars(project['stars']) }}</span></div>
{% if project['language'] and project['language'] != 'N/A' %}
<div class="flex items-center gap-1.5">
<span class="w-2.5 h-2.5 rounded-full {{ language_colors.get(project['language'], 'bg-gray-400') }}"></span>
<span>{{ project['language'] }}</span>
</div>
{% endif %}
</div>
</a>
{% else %}
<div class="col-span-full flex flex-col px-4 py-16 items-center justify-center">
<div class="flex flex-col items-center gap-6 text-center">
<div class="text-slate-400 dark:text-slate-600"><span class="material-symbols-outlined" style="font-size: 96px;">search_off</span></div>
<div class="flex max-w-md flex-col items-center gap-2">
<p class="text-slate-900 dark:text-white text-xl font-bold leading-tight tracking-[-0.015em]">未找到项目</p>
<p class="text-slate-600 dark:text-slate-400 text-sm font-normal leading-normal">数据库为空或没有项目匹配您的筛选条件。</p>
</div>
</div>
</div>
{% endfor %}
</div>
{% if total_pages > 1 %}
<nav class="flex justify-center items-center gap-2 mt-6">
{% if current_page > 1 %}
<a href="/?page={{ current_page - 1 }}&sort={{ current_sort }}&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-get="/?page={{ current_page - 1 }}&sort={{ current_sort }}&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true"
class="pagination-link pagination-link-inactive hover:bg-slate-300 dark:hover:bg-slate-600"></a>
{% else %}
<span class="pagination-link pagination-link-disabled"></span>
{% endif %}
{% for page_num in range(1, total_pages + 1) %}
{% if page_num == current_page %}
<span class="pagination-link pagination-link-active">{{ page_num }}</span>
{% elif page_num > current_page - 3 and page_num < current_page + 3 %}
<a href="/?page={{ page_num }}&sort={{ current_sort }}&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-get="/?page={{ page_num }}&sort={{ current_sort }}&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true"
class="pagination-link pagination-link-inactive hover:bg-slate-300 dark:hover:bg-slate-600">{{ page_num }}</a>
{% elif page_num == current_page - 3 and page_num > 1 %}
<span class="pagination-link">...</span>
{% elif page_num == current_page + 3 and page_num < total_pages %}
<span class="pagination-link">...</span>
{% endif %}
{% endfor %}
{% if current_page < total_pages %}
<a href="/?page={{ current_page + 1 }}&sort={{ current_sort }}&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-get="/?page={{ current_page + 1 }}&sort={{ current_sort }}&q={{ search_query }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true"
class="pagination-link pagination-link-inactive hover:bg-slate-300 dark:hover:bg-slate-600"></a>
{% else %}
<span class="pagination-link pagination-link-disabled"></span>
{% endif %}
</nav>
{% endif %}

103
templates/index.html Normal file
View File

@@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>CodeZen - 中文项目发现</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet"/>
<script src="https://unpkg.com/htmx.org@1.9.12" integrity="sha384-ujb1lZICHoG1rqPyoGrfglLcuPMLrP8okbNtGHJSAzJDPXwLSPM0i7MFLdeTqUYk" crossorigin="anonymous"></script>
<script>
tailwind.config = { darkMode: "class", theme: { extend: { colors: { "primary": "#137fec", "background-light": "#f6f7f8", "background-dark": "#101922" }, fontFamily: {"display": ["Inter", "sans-serif"]}, borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"} } } }
</script>
<style>
.material-symbols-outlined {font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; font-size: 20px;}
.no-underline {text-decoration: none;}
.filter-button-active { background-color: #137fec !important; color: white !important; }
.dark .filter-button-active { background-color: #137fec !important; color: white !import
ant; }
.filter-button-active > div { color: white !important; }
.filter-button-active > p { color: white !important; }
.pagination-link { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 0.5rem; transition: background-color 0.2s; }
.pagination-link-active { background-color: #137fec; color: white; cursor: default; }
.pagination-link-inactive { background-color: #e2e8f0; }
.dark .pagination-link-inactive { background-color: #1e293b; }
.pagination-link-disabled { color: #94a3b8; background-color: #f1f5f9; cursor: not-allowed; }
.dark .pagination-link-disabled { color: #475569; background-color: #1a2431; cursor: not-allowed; }
#theme-toggle-sun, #theme-toggle-moon { display: none; }
.dark #theme-toggle-sun { display: block; }
html:not(.dark) #theme-toggle-moon { display: block; }
</style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display">
<div class="relative flex h-auto min-h-screen w-full flex-col">
<div class="layout-container flex h-full grow flex-col">
<div class="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-5">
<div class="layout-content-container flex flex-col flex-1">
<header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-slate-200 dark:border-slate-800 px-4 sm:px-6 lg:px-10 py-3">
<a href="/" class="no-underline flex items-center gap-4 text-slate-800 dark:text-white">
<div class="size-6 text-primary"><span class="material-symbols-outlined" style="font-size: 24px;">hub</span></div>
<h2 class="text-slate-800 dark:text-white text-lg font-bold leading-tight tracking-[-0.015em]">CodeZen</h2>
</a>
<div class="flex flex-1 justify-end items-center gap-4 sm:gap-6">
<form id="search-form" action="/" method="get" class="hidden sm:flex flex-col min-w-40 !h-10 max-w-64"
hx-get="/?sort={{ current_sort }}&lang={{ current_lang|urlencode }}&topic={{ current_topic|urlencode }}"
hx-target="#main-content"
hx-swap="innerHTML"
hx-push-url="true"
hx-trigger="keyup[target.name=='q'] changed[target.name=='q'] delay:500ms">
<div class="flex w-full flex-1 items-stretch rounded-lg h-full">
<div class="text-slate-500 dark:text-slate-400 flex border border-r-0 border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800/50 items-center justify-center pl-3 rounded-l-lg"><span class="material-symbols-outlined">search</span></div>
<input name="q" class="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-slate-800 dark:text-white focus:outline-0 border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800/50 h-full placeholder:text-slate-400 dark:placeholder:text-slate-500 px-4 rounded-l-none border-l-0 pl-2 text-base font-normal leading-normal" placeholder="搜索项目..." value="{{ search_query }}"/>
</div>
</form>
<button id="theme-toggle" type="button" class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors duration-200">
<span id="theme-toggle-sun" class="material-symbols-outlined">light_mode</span>
<span id="theme-toggle-moon" class="material-symbols-outlined">dark_mode</span>
</button>
</div>
</header>
<main id="main-content" class="p-4 sm:p-6 lg:p-10 flex flex-col gap-6">
{% include '_projects_partial.html' %}
</main>
<footer class="text-center p-6 mt-auto text-sm text-slate-500 dark:text-slate-600 border-t border-slate-200 dark:border-slate-800">
<p>© {{ current_year }} CodeZen. 数据来源 GitHub.</p>
<p class="text-xs text-slate-400 dark:text-slate-700 mt-2">本页面数据由脚本自动抓取和汇总,非实时更新。</p>
</footer>
</div>
</div>
</div>
</div>
<script>
(function() {
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
})();
window.onload = function() {
document.getElementById('theme-toggle').addEventListener('click', function() {
var html = document.documentElement;
if (html.classList.contains('dark')) {
html.classList.remove('dark');
localStorage.theme = 'light';
} else {
html.classList.add('dark');
localStorage.theme = 'dark';
}
});
}
</script>
</body>
</html>