From 5ecf0f8adc7434fd49bf7ef6829715b6bb804b85 Mon Sep 17 00:00:00 2001 From: FenjuFu Date: Thu, 23 Jul 2026 12:40:40 +0800 Subject: [PATCH 1/2] seo: URL-driven list filters and per-page meta descriptions Make filtered views of the projects and events pages crawlable and shareable by moving filter state into URL query parameters: - /projects: category/language filters are now server-rendered links (?category=...&language=...) filtered on the server, replacing the client-side JS show/hide script; add an empty-state with a clear link - /events: switch to server rendering with ?type=... filter links, replacing the client-side filter script; event status (upcoming/ ongoing/past) is now computed at request time instead of being frozen at build time Canonical URLs already strip query strings, so filtered views canonicalize to the base list page and add no duplicate content. Also add unique meta descriptions to pages that previously fell back to the site-wide default: projects, events, adopters, landscape, contribute, CLA, values, security and the blog list/category/tag pages (MarkdownLayout now passes description through). Signed-off-by: FenjuFu --- src/layouts/MarkdownLayout.astro | 2 + src/pages/[...blog]/[...page].astro | 2 + .../[...blog]/[category]/[...page].astro | 16 +- src/pages/[...blog]/[tag]/[...page].astro | 1 + src/pages/adopters.astro | 2 + src/pages/cla.astro | 9 +- src/pages/contribute.astro | 2 + src/pages/events.astro | 96 +++++------ src/pages/landscape.astro | 2 + src/pages/projects.astro | 149 +++++++++--------- src/pages/security.astro | 9 +- src/pages/values.astro | 9 +- 12 files changed, 157 insertions(+), 142 deletions(-) diff --git a/src/layouts/MarkdownLayout.astro b/src/layouts/MarkdownLayout.astro index 49ba81c..2cbcdb6 100644 --- a/src/layouts/MarkdownLayout.astro +++ b/src/layouts/MarkdownLayout.astro @@ -7,6 +7,7 @@ export interface Props { frontmatter: { title?: string; titleEn?: string; + description?: string; }; } @@ -14,6 +15,7 @@ const { frontmatter } = Astro.props; const metadata: MetaData = { title: frontmatter?.title, + description: frontmatter?.description, }; --- diff --git a/src/pages/[...blog]/[...page].astro b/src/pages/[...blog]/[...page].astro index e449371..d381667 100644 --- a/src/pages/[...blog]/[...page].astro +++ b/src/pages/[...blog]/[...page].astro @@ -23,6 +23,8 @@ const currentPage = page.currentPage ?? 1; const metadata = { title: `Blog${currentPage > 1 ? ` — Page ${currentPage}` : ''}`, + description: + '讯飞开源博客 — Articles from the iFLYTEK open source community: technical deep dives, project updates, release notes and community news.', robots: { index: blogListRobots?.index && currentPage === 1, follow: blogListRobots?.follow, diff --git a/src/pages/[...blog]/[category]/[...page].astro b/src/pages/[...blog]/[category]/[...page].astro index 4d3bc52..bb7dfc9 100644 --- a/src/pages/[...blog]/[category]/[...page].astro +++ b/src/pages/[...blog]/[category]/[...page].astro @@ -18,13 +18,6 @@ const { page, category } = Astro.props as Props; const currentPage = page.currentPage ?? 1; -const metadata = { - title: `Category '${category.title}' ${currentPage > 1 ? ` — Page ${currentPage}` : ''}`, - robots: { - index: blogCategoryRobots?.index, - follow: blogCategoryRobots?.follow, - }, -}; const categoryMeta: Record = { tech: { titleZh: '技术博客', @@ -48,6 +41,15 @@ const cat = categoryMeta[category.slug] || { description: `${category.title} 相关文章`, descriptionEn: `${category.title} related posts`, }; + +const metadata = { + title: `Category '${category.title}' ${currentPage > 1 ? ` — Page ${currentPage}` : ''}`, + description: `${cat.description} — ${cat.descriptionEn}`, + robots: { + index: blogCategoryRobots?.index, + follow: blogCategoryRobots?.follow, + }, +}; --- diff --git a/src/pages/[...blog]/[tag]/[...page].astro b/src/pages/[...blog]/[tag]/[...page].astro index 86a767b..2b61d7f 100644 --- a/src/pages/[...blog]/[tag]/[...page].astro +++ b/src/pages/[...blog]/[tag]/[...page].astro @@ -21,6 +21,7 @@ const currentPage = page.currentPage ?? 1; const metadata = { title: `Posts by tag '${tag.title}'${currentPage > 1 ? ` — Page ${currentPage} ` : ''}`, + description: `标签 '${tag.title}' 下的文章 — Posts tagged '${tag.title}' from the iFLYTEK open source blog.`, robots: { index: blogTagRobots?.index, follow: blogTagRobots?.follow, diff --git a/src/pages/adopters.astro b/src/pages/adopters.astro index 335a0e6..0a16c46 100644 --- a/src/pages/adopters.astro +++ b/src/pages/adopters.astro @@ -4,6 +4,8 @@ import { getCollection } from 'astro:content'; const metadata = { title: 'Adopters', + description: + '谁在使用讯飞开源项目 — Companies and organizations adopting iFLYTEK open source projects in production, and how to add your organization to the list.', }; const adopters = await getCollection('adopter'); diff --git a/src/pages/cla.astro b/src/pages/cla.astro index 390f346..d5ac734 100644 --- a/src/pages/cla.astro +++ b/src/pages/cla.astro @@ -2,7 +2,14 @@ import MarkdownLayout from '~/layouts/MarkdownLayout.astro'; --- - +

diff --git a/src/pages/contribute.astro b/src/pages/contribute.astro index 566f026..eb18291 100644 --- a/src/pages/contribute.astro +++ b/src/pages/contribute.astro @@ -9,6 +9,8 @@ export const prerender = false; const metadata = { title: 'Contribute', + description: + '参与贡献科大讯飞开源社区 — How to contribute to iFLYTEK open source: find good first issues, submit pull requests, sign the CLA and join the community.', }; // Load projects count diff --git a/src/pages/events.astro b/src/pages/events.astro index 97df25e..f878cfb 100644 --- a/src/pages/events.astro +++ b/src/pages/events.astro @@ -1,21 +1,26 @@ --- import Layout from '~/layouts/PageLayout.astro'; import { getCollection } from 'astro:content'; -import { events } from '~/data/events'; +import { events as eventsData } from '~/data/events'; + +export const prerender = false; const metadata = { title: 'Events', + description: + '科大讯飞开源社区活动 — iFLYTEK open source community events: meetups, hackathons, workshops and conferences, with the latest community news.', }; // Auto-determine status based on event date vs current date const now = new Date(); const todayStr = now.toISOString().split('T')[0]; -events.forEach((event) => { - event.status = event.date === todayStr ? 'ongoing' : event.date > todayStr ? 'upcoming' : 'past'; -}); - -// Sort by date descending -events.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); +const events = eventsData + .map((event) => ({ + ...event, + status: (event.date === todayStr ? 'ongoing' : event.date > todayStr ? 'upcoming' : 'past') as + 'ongoing' | 'upcoming' | 'past', + })) + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); const typeLabels: Record = { conference: 'Conference', @@ -36,6 +41,15 @@ const typeColors: Record = { // Get unique types for filter const eventTypes = [...new Set(events.map((e) => e.type))]; +// URL-driven filter state so filtered views are crawlable and shareable +const typeParam = Astro.url.searchParams.get('type') || 'all'; +const activeType = (eventTypes as string[]).includes(typeParam) ? typeParam : 'all'; +const filteredEvents = activeType === 'all' ? events : events.filter((e) => e.type === activeType); + +const activeChipClass = 'bg-primary-600 text-white'; +const inactiveChipClass = + 'bg-gray-100 text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700'; + // Blog posts for news section const posts = await getCollection('post'); const newsPosts = posts @@ -75,22 +89,28 @@ const newsPosts = posts

- + { eventTypes.map((type) => ( - + )) }
@@ -98,8 +118,8 @@ const newsPosts = posts
{ - events.map((event, index) => ( -
+ filteredEvents.map((event, index) => ( +
{/* Timeline line & dot */}
@@ -114,7 +134,7 @@ const newsPosts = posts ]} />
- {index < events.length - 1 &&
} + {index < filteredEvents.length - 1 &&
}
{/* Event Card */} @@ -251,44 +271,4 @@ const newsPosts = posts ) } - - - diff --git a/src/pages/landscape.astro b/src/pages/landscape.astro index b545eaf..6503e35 100644 --- a/src/pages/landscape.astro +++ b/src/pages/landscape.astro @@ -4,6 +4,8 @@ import { getProjectsWithStats } from '~/utils/project-stats'; const metadata = { title: 'Landscape', + description: + '讯飞开源全景图 — The iFLYTEK open source landscape: an at-a-glance map of all projects organized by category, from AI Agent platforms to developer tools.', }; const projects = await getProjectsWithStats(); diff --git a/src/pages/projects.astro b/src/pages/projects.astro index 2f6470f..e02b416 100644 --- a/src/pages/projects.astro +++ b/src/pages/projects.astro @@ -7,6 +7,8 @@ export const prerender = false; const metadata = { title: 'Projects', + description: + '探索科大讯飞开源项目生态 — Explore iFLYTEK open source projects covering AI Agent, agentic workflow, developer tools and more, filterable by category and language.', }; const projects = await getProjectsWithStats(); @@ -34,6 +36,31 @@ const categoryLabels: Record = { // Total stats const totalStars = sortedProjects.reduce((sum, p) => sum + (p.data.stars || 0), 0); const totalForks = sortedProjects.reduce((sum, p) => sum + (p.data.forks || 0), 0); + +// URL-driven filter state so filtered views are crawlable and shareable +const { searchParams } = Astro.url; +const categoryParam = searchParams.get('category') || 'all'; +const languageParam = searchParams.get('language') || 'all'; +const activeCategory = categories.includes(categoryParam) ? categoryParam : 'all'; +const activeLanguage = languages.includes(languageParam) ? languageParam : 'all'; + +const filteredProjects = sortedProjects.filter( + (p) => + (activeCategory === 'all' || p.data.category === activeCategory) && + (activeLanguage === 'all' || (p.data.languages || []).includes(activeLanguage)) +); + +const buildFilterUrl = (category: string, language: string) => { + const params = new URLSearchParams(); + if (category !== 'all') params.set('category', category); + if (language !== 'all') params.set('language', language); + const query = params.toString(); + return query ? `/projects?${query}` : '/projects'; +}; + +const activeChipClass = 'bg-primary-600 text-white'; +const inactiveChipClass = + 'bg-gray-100 text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700'; --- @@ -85,26 +112,32 @@ const totalForks = sortedProjects.reduce((sum, p) => sum + (p.data.forks || 0),
- + { categories.map((cat) => ( - + )) }
@@ -113,21 +146,27 @@ const totalForks = sortedProjects.reduce((sum, p) => sum + (p.data.forks || 0), { languages.length > 0 && (
- + {languages.map((lang) => ( - + ))}
) @@ -136,12 +175,8 @@ const totalForks = sortedProjects.reduce((sum, p) => sum + (p.data.forks || 0),
{ - sortedProjects.map((project) => ( -
+ filteredProjects.map((project) => ( +
sum + (p.data.forks || 0), }
+ { + filteredProjects.length === 0 && ( +
+

+ 没有符合当前筛选条件的项目 +

+ + + 清除筛选 + + +
+ ) + } +
- - - diff --git a/src/pages/security.astro b/src/pages/security.astro index 6a4d643..eeff4f5 100644 --- a/src/pages/security.astro +++ b/src/pages/security.astro @@ -2,7 +2,14 @@ import MarkdownLayout from '~/layouts/MarkdownLayout.astro'; --- - +

如果你在本项目中发现安全问题,请遵循以下指南。

diff --git a/src/pages/values.astro b/src/pages/values.astro index 7151f27..d5ae409 100644 --- a/src/pages/values.astro +++ b/src/pages/values.astro @@ -2,7 +2,14 @@ import MarkdownLayout from '~/layouts/MarkdownLayout.astro'; --- - +

科大讯飞开源社区建立在我们共同遵循的价值观之上,这些价值观指引着我们的互动、决策与成长。

From 05dc87e8c31c5e4679bde0f9b4d2b180550d6c6e Mon Sep 17 00:00:00 2001 From: FenjuFu Date: Fri, 24 Jul 2026 11:29:43 +0800 Subject: [PATCH 2/2] docs: update changelog for SEO filters Signed-off-by: FenjuFu --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 920dff6..d722d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- **SEO-Friendly Project and Event Filters**: Replace client-side-only filters with server-rendered, shareable query-string URLs for project category/language and event type; add explicit empty states, request-time event status calculation, and page-specific bilingual meta descriptions across list, policy, and blog pages + ## [0.4.0] - 2026-07-17 ### Added