diff --git a/README.en.md b/README.en.md
index 775e276..7203d6e 100644
--- a/README.en.md
+++ b/README.en.md
@@ -46,7 +46,7 @@ homepage
## Contribution Guide
-See [CONTRIBUTING.md](docs/zh-CN/CONTRIBUTING.md) in the `docs` directory to learn how to participate in the project.
+See [CONTRIBUTING.md](docs/en-US/CONTRIBUTING.md) in the `docs` directory to learn how to participate in the project.
## License
diff --git a/README.zh-Hant.md b/README.zh-Hant.md
index 3cd8132..b55b296 100644
--- a/README.zh-Hant.md
+++ b/README.zh-Hant.md
@@ -46,7 +46,7 @@ homepage
## 貢獻指南
-參見 `docs` 目錄中的 [CONTRIBUTING.md](docs/zh-CN/CONTRIBUTING.md) 了解如何參與專案。
+參見 `docs` 目錄中的 [CONTRIBUTING.md](docs/zh-TW/CONTRIBUTING.md) 了解如何參與專案。
## 許可
diff --git a/index.html b/index.html
index 7be636e..1d18309 100644
--- a/index.html
+++ b/index.html
@@ -5,6 +5,7 @@
STARSET Mirror
+
diff --git a/package.json b/package.json
index 9645731..1c30b6c 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
- "build": "vite build",
+ "build": "vite build && tsx scripts/generate-rss.ts",
"lint": "eslint .",
"preview": "vite preview"
},
@@ -14,6 +14,7 @@
"@tanstack/react-query": "^5.66.0",
"antd": "^5.23.3",
"clsx": "^2.1.1",
+ "feed": "^4.2.2",
"i18next": "^23.16.8",
"i18next-browser-languagedetector": "^7.2.2",
"lucide-react": "^0.344.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 16e3736..a00a9f5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
+ feed:
+ specifier: ^4.2.2
+ version: 4.2.2
i18next:
specifier: ^23.16.8
version: 23.16.8
@@ -1138,6 +1141,10 @@ packages:
fastq@1.18.0:
resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==}
+ feed@4.2.2:
+ resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==}
+ engines: {node: '>=0.4.0'}
+
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@@ -1845,6 +1852,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ sax@1.4.1:
+ resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
+
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
@@ -2039,6 +2049,10 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
+ xml-js@1.6.11:
+ resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
+ hasBin: true
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -3103,6 +3117,10 @@ snapshots:
dependencies:
reusify: 1.0.4
+ feed@4.2.2:
+ dependencies:
+ xml-js: 1.6.11
+
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@@ -3857,6 +3875,8 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
+ sax@1.4.1: {}
+
scheduler@0.23.2:
dependencies:
loose-envify: 1.4.0
@@ -4038,6 +4058,10 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.1.0
+ xml-js@1.6.11:
+ dependencies:
+ sax: 1.4.1
+
yallist@3.1.1: {}
yaml@2.7.0: {}
diff --git a/scripts/generate-rss.ts b/scripts/generate-rss.ts
new file mode 100644
index 0000000..449fb54
--- /dev/null
+++ b/scripts/generate-rss.ts
@@ -0,0 +1,103 @@
+import { Feed } from 'feed';
+import fs from 'fs-extra';
+import path from 'path';
+import { Update } from '../src/utils/updates';
+
+const SITE_URL = 'https://starset.wiki';
+
+interface LanguageConfig {
+ code: string;
+ dataDir: string;
+ title: string;
+ description: string;
+}
+
+const LANGUAGES: LanguageConfig[] = [
+ {
+ code: 'en-US',
+ dataDir: 'en-US',
+ title: 'STARSET Mirror Site Updates',
+ description: 'Latest updates from STARSET Mirror Site'
+ },
+ {
+ code: 'zh-CN',
+ dataDir: 'zh-CN',
+ title: 'STARSET 镜像站更新',
+ description: 'STARSET 镜像站的最新更新'
+ },
+ {
+ code: 'zh-Hant',
+ dataDir: 'zh-TW',
+ title: 'STARSET 鏡像站更新',
+ description: 'STARSET 鏡像站的最新更新'
+ }
+];
+
+async function generateRSSFeed(lang: LanguageConfig) {
+ // Read all updates
+ const updatesIndex = await fs.readJson(path.join('data', lang.dataDir, 'updates.json'));
+ const years = updatesIndex.years as string[];
+
+ let allUpdates: Update[] = [];
+ for (const year of years) {
+ const yearData = await fs.readJson(path.join('data', lang.dataDir, 'updates', year));
+ allUpdates = [...allUpdates, ...yearData.updates];
+ }
+
+ // Sort updates by date in descending order
+ allUpdates.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
+
+ // Take the latest 10 updates
+ const latestUpdates = allUpdates.slice(0, 10);
+
+ // Create feed
+ const feed = new Feed({
+ title: lang.title,
+ description: lang.description,
+ id: `${SITE_URL}/${lang.code}/updates`,
+ link: `${SITE_URL}/${lang.code}/updates`,
+ language: lang.code,
+ favicon: `${SITE_URL}/favicon.ico`,
+ copyright: "All rights reserved",
+ updated: latestUpdates[0] ? new Date(latestUpdates[0].date) : new Date(),
+ feedLinks: {
+ rss2: `${SITE_URL}/${lang.code}/rss.xml`
+ }
+ });
+
+ // Add items to feed
+ for (const update of latestUpdates) {
+ feed.addItem({
+ title: update.title,
+ id: update.id,
+ link: update.link || `${SITE_URL}/${lang.code}/updates#${update.id}`,
+ description: update.summary,
+ date: new Date(update.date),
+ category: update.tags.map(tag => ({ name: tag }))
+ });
+ }
+
+ // Create dist directory if it doesn't exist
+ const distPath = path.join('dist', lang.code);
+ await fs.ensureDir(distPath);
+
+ // Write feed to file
+ await fs.writeFile(
+ path.join(distPath, 'rss.xml'),
+ feed.rss2()
+ );
+}
+
+async function main() {
+ try {
+ for (const lang of LANGUAGES) {
+ await generateRSSFeed(lang);
+ console.log(`Generated RSS feed for ${lang.code}`);
+ }
+ } catch (error) {
+ console.error('Error generating RSS feeds:', error);
+ process.exit(1);
+ }
+}
+
+main();
diff --git a/src/components/Timeline.tsx b/src/components/Timeline.tsx
index e9157e1..fb4090f 100644
--- a/src/components/Timeline.tsx
+++ b/src/components/Timeline.tsx
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useSearchParams } from 'react-router-dom';
import { ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react';
+import { RssIcon } from 'lucide-react'; // Add this line
import { Update, useUpdates, getUpdateUrl } from '../utils/updates';
import TagFilter from './ui/TagFilter';
@@ -60,6 +61,9 @@ const Pagination: React.FC = ({
const Timeline = () => {
const { t, i18n } = useTranslation();
+ const LANGUAGE_CODE_MAP: Record = {
+ 'zh-TW': 'zh-Hant'
+ };
const [searchParams, setSearchParams] = useSearchParams();
// 获取当前页码和标签筛选
@@ -117,16 +121,29 @@ const Timeline = () => {
return (
-
-
- {t('updates.title')}
-
+
+
+
+ {t('updates.title')}
+
+
+
+
+
-
+
+
+
diff --git a/src/components/ui/TagFilter.tsx b/src/components/ui/TagFilter.tsx
index f1995f2..f7b8ecd 100644
--- a/src/components/ui/TagFilter.tsx
+++ b/src/components/ui/TagFilter.tsx
@@ -59,26 +59,26 @@ const TagFilter: React.FC
= ({
return (
-
+
{selectedTags.length > 0 && (
-
+
{selectedTags.map(tag => (
{t(`updates.tags.${tag}`)}
))}
@@ -86,13 +86,13 @@ const TagFilter: React.FC = ({
)}