chore: checkpoint ai search comments and i18n foundation

This commit is contained in:
2026-03-28 17:17:31 +08:00
parent d18a709987
commit ec96d91548
71 changed files with 9494 additions and 423 deletions

View File

@@ -4,8 +4,8 @@ import TerminalWindow from '../../components/ui/TerminalWindow.astro';
import CommandPrompt from '../../components/ui/CommandPrompt.astro';
import FilterPill from '../../components/ui/FilterPill.astro';
import PostCard from '../../components/PostCard.astro';
import { terminalConfig } from '../../lib/config/terminal';
import { api } from '../../lib/api/client';
import { getI18n } from '../../lib/i18n';
import type { Category, Post, Tag } from '../../lib/types';
export const prerender = false;
@@ -15,6 +15,7 @@ let allTags: Tag[] = [];
let allCategories: Category[] = [];
const url = new URL(Astro.request.url);
const selectedSearch = url.searchParams.get('search') || '';
const { t } = getI18n(Astro);
try {
allPosts = selectedSearch ? await api.searchPosts(selectedSearch) : await api.getPosts();
@@ -54,9 +55,9 @@ const startIndex = (currentPage - 1) * postsPerPage;
const paginatedPosts = filteredPosts.slice(startIndex, startIndex + postsPerPage);
const postTypeFilters = [
{ id: 'all', name: '全部', icon: 'fa-stream' },
{ id: 'article', name: terminalConfig.postTypes.article.label, icon: 'fa-file-alt' },
{ id: 'tweet', name: terminalConfig.postTypes.tweet.label, icon: 'fa-comment-dots' }
{ id: 'all', name: t('common.all'), icon: 'fa-stream' },
{ id: 'article', name: t('common.article'), icon: 'fa-file-alt' },
{ id: 'tweet', name: t('common.tweet'), icon: 'fa-comment-dots' }
];
const typePromptCommand = `./filter --type ${selectedType || 'all'}`;
@@ -89,21 +90,21 @@ const buildArticlesUrl = ({
};
---
<BaseLayout title="文章列表 - Termi">
<BaseLayout title={`${t('articlesPage.title')} - Termi`}>
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<TerminalWindow title="~/articles/index" class="w-full">
<div class="px-4 pb-2">
<CommandPrompt command="fd . ./content/posts --full-path" />
<div class="ml-4 mt-4 space-y-3">
<h1 class="text-3xl font-bold tracking-tight text-[var(--title-color)]">文章索引</h1>
<h1 class="text-3xl font-bold tracking-tight text-[var(--title-color)]">{t('articlesPage.title')}</h1>
<p class="max-w-2xl text-sm leading-7 text-[var(--text-secondary)]">
按类型、分类和标签筛选内容。这里保留更轻的 prompt 标题结构,下方筛选拆成独立区域。
{t('articlesPage.description')}
</p>
<div class="flex flex-wrap gap-2">
<span class="terminal-stat-pill">
<i class="fas fa-file-lines text-[var(--primary)]"></i>
共 {filteredPosts.length} 篇
{t('articlesPage.totalPosts', { count: filteredPosts.length })}
</span>
{selectedSearch && (
<span class="terminal-stat-pill">
@@ -154,7 +155,7 @@ const buildArticlesUrl = ({
active={!selectedCategory}
>
<i class="fas fa-folder-tree"></i>
<span class="font-medium">全部分类</span>
<span class="font-medium">{t('articlesPage.allCategories')}</span>
</FilterPill>
{allCategories.map(category => (
<FilterPill
@@ -181,7 +182,7 @@ const buildArticlesUrl = ({
active={!selectedTag}
>
<i class="fas fa-hashtag"></i>
<span class="font-medium">全部标签</span>
<span class="font-medium">{t('articlesPage.allTags')}</span>
</FilterPill>
{allTags.map(tag => (
<FilterPill
@@ -211,13 +212,13 @@ const buildArticlesUrl = ({
<span class="terminal-section-icon">
<i class="fas fa-folder-open"></i>
</span>
<h2 class="text-xl font-semibold text-[var(--title-color)]">没有匹配结果</h2>
<h2 class="text-xl font-semibold text-[var(--title-color)]">{t('articlesPage.emptyTitle')}</h2>
<p class="text-sm leading-7 text-[var(--text-secondary)]">
当前筛选条件下没有找到文章。可以清空标签或关键字,重新浏览整个内容目录。
{t('articlesPage.emptyDescription')}
</p>
<a href="/articles" class="terminal-action-button terminal-action-button-primary">
<i class="fas fa-rotate-left"></i>
<span>reset filters</span>
<span>{t('common.resetFilters')}</span>
</a>
</div>
</div>
@@ -228,7 +229,7 @@ const buildArticlesUrl = ({
<div class="px-4 py-6">
<div class="terminal-panel-muted ml-4 mt-4 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<span class="text-sm text-[var(--text-secondary)]">
page {currentPage}/{totalPages} · {totalPosts} results
{t('articlesPage.pageSummary', { current: currentPage, total: totalPages, count: totalPosts })}
</span>
<div class="flex flex-wrap gap-2">
{currentPage > 1 && (
@@ -237,7 +238,7 @@ const buildArticlesUrl = ({
class="terminal-action-button"
>
<i class="fas fa-chevron-left"></i>
<span>prev</span>
<span>{t('articlesPage.previous')}</span>
</a>
)}
{currentPage < totalPages && (
@@ -245,7 +246,7 @@ const buildArticlesUrl = ({
href={buildArticlesUrl({ page: currentPage + 1 })}
class="terminal-action-button terminal-action-button-primary"
>
<span>next</span>
<span>{t('articlesPage.next')}</span>
<i class="fas fa-chevron-right"></i>
</a>
)}