Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file.

### 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
- **Astro 6 → 7 Major Upgrade**: `astro` ^6.4.8 → ^7.1.3, `@astrojs/vercel` ^10.0.8 → ^11.0.3, `@astrojs/mdx` ^6.0.3 → ^7.0.3 (Astro 7 ships with Vite 8 + Rolldown, stricter SSR static-build validation)
- **astro-compress Upgrade**: ^2.3.8 → ^2.4.1 (picks up svgo 4.x, sharp 0.34.x)
- **npm overrides Update**: Added `"svgo": ">=4.0.2"` and `"sharp": "^0.35.3"` overrides to force transitive dependencies to safe versions; removed conflicting `"vite": "^7"` override (Astro 7 requires Vite 8)
Expand Down
2 changes: 2 additions & 0 deletions src/layouts/MarkdownLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export interface Props {
frontmatter: {
title?: string;
titleEn?: string;
description?: string;
};
}

const { frontmatter } = Astro.props;

const metadata: MetaData = {
title: frontmatter?.title,
description: frontmatter?.description,
};
---

Expand Down
2 changes: 2 additions & 0 deletions src/pages/[...blog]/[...page].astro
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 9 additions & 7 deletions src/pages/[...blog]/[category]/[...page].astro
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, { titleZh: string; titleEn: string; description: string; descriptionEn: string }> = {
tech: {
titleZh: '技术博客',
Expand All @@ -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,
},
};
---

<Layout metadata={metadata}>
Expand Down
1 change: 1 addition & 0 deletions src/pages/[...blog]/[tag]/[...page].astro
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/pages/adopters.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
9 changes: 8 additions & 1 deletion src/pages/cla.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
import MarkdownLayout from '~/layouts/MarkdownLayout.astro';
---

<MarkdownLayout frontmatter={{ title: '贡献者许可协议', titleEn: 'Contributor License Agreement' }}>
<MarkdownLayout
frontmatter={{
title: '贡献者许可协议',
titleEn: 'Contributor License Agreement',
description:
Comment thread
FenjuFu marked this conversation as resolved.
Outdated
'科大讯飞开源社区贡献者许可协议(CLA)— The iFLYTEK open source Contributor License Agreement covering copyright and patent license grants for contributions.',
}}
>
<!-- Chinese Content -->
<div class="i18n-zh">
<p>
Expand Down
2 changes: 2 additions & 0 deletions src/pages/contribute.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
96 changes: 38 additions & 58 deletions src/pages/events.astro
Original file line number Diff line number Diff line change
@@ -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<string, string> = {
conference: 'Conference',
Expand All @@ -36,6 +41,15 @@ const typeColors: Record<string, string> = {
// 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
Expand Down Expand Up @@ -75,31 +89,37 @@ const newsPosts = posts
<div class="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
<!-- Filter Tabs -->
<div class="mb-10 flex flex-wrap items-center justify-center gap-3">
<button
class="event-filter-btn rounded-full bg-primary-600 px-5 py-2 text-sm font-medium text-white"
data-type="all"
<a
href="/events"
class:list={[
'rounded-full px-5 py-2 text-sm font-medium',
activeType === 'all' ? activeChipClass : inactiveChipClass,
]}
>
<span data-lang-zh={`全部 (${events.length})`} data-lang-en={`All (${events.length})`}
>全部 ({events.length})</span
>
</button>
</a>
{
eventTypes.map((type) => (
<button
class="event-filter-btn rounded-full bg-gray-100 px-5 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
data-type={type}
<a
href={`/events?type=${type}`}
class:list={[
'rounded-full px-5 py-2 text-sm font-medium',
activeType === type ? activeChipClass : inactiveChipClass,
]}
>
{typeLabels[type]} ({events.filter((e) => e.type === type).length})
</button>
</a>
))
}
</div>

<!-- Timeline -->
<div class="space-y-1">
{
events.map((event, index) => (
<div class="event-item relative flex gap-4 sm:gap-8" data-type={event.type}>
filteredEvents.map((event, index) => (
<div class="relative flex gap-4 sm:gap-8">
{/* Timeline line & dot */}
<div class="flex flex-col items-center">
<div class="relative z-10 flex h-10 w-10 flex-shrink-0 items-center justify-center">
Expand All @@ -114,7 +134,7 @@ const newsPosts = posts
]}
/>
</div>
{index < events.length - 1 && <div class="w-px flex-1 bg-gray-200 dark:bg-gray-800" />}
{index < filteredEvents.length - 1 && <div class="w-px flex-1 bg-gray-200 dark:bg-gray-800" />}
</div>

{/* Event Card */}
Expand Down Expand Up @@ -257,44 +277,4 @@ const newsPosts = posts
</section>
)
}

<!-- Filter Script -->
<script is:inline>
(function () {
function setupFilters() {
const buttons = document.querySelectorAll('.event-filter-btn');
const items = document.querySelectorAll('.event-item');

buttons.forEach((btn) => {
btn.addEventListener('click', () => {
const type = btn.dataset.type;

// Update button styles
buttons.forEach((b) => {
b.classList.remove('bg-primary-600', 'text-white');
b.classList.add('bg-gray-100', 'text-gray-700', 'dark:bg-gray-800', 'dark:text-gray-300');
});
btn.classList.add('bg-primary-600', 'text-white');
btn.classList.remove('bg-gray-100', 'text-gray-700', 'dark:bg-gray-800', 'dark:text-gray-300');

// Filter items
items.forEach((item) => {
const itemType = item.dataset.type;
if (type === 'all' || itemType === type) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
});
});
}

if (document.readyState !== 'loading') {
setupFilters();
} else {
document.addEventListener('DOMContentLoaded', setupFilters);
}
})();
</script>
</Layout>
2 changes: 2 additions & 0 deletions src/pages/landscape.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading
Loading