parent
e30d41db2f
commit
4ba16a7ddd
4 changed files with 147 additions and 4 deletions
|
@ -5,7 +5,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build && tsx scripts/generate-rss.ts",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
|
@ -14,6 +14,7 @@
|
||||||
"@tanstack/react-query": "^5.66.0",
|
"@tanstack/react-query": "^5.66.0",
|
||||||
"antd": "^5.23.3",
|
"antd": "^5.23.3",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"feed": "^4.2.2",
|
||||||
"i18next": "^23.16.8",
|
"i18next": "^23.16.8",
|
||||||
"i18next-browser-languagedetector": "^7.2.2",
|
"i18next-browser-languagedetector": "^7.2.2",
|
||||||
"lucide-react": "^0.344.0",
|
"lucide-react": "^0.344.0",
|
||||||
|
|
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
|
@ -20,6 +20,9 @@ importers:
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
feed:
|
||||||
|
specifier: ^4.2.2
|
||||||
|
version: 4.2.2
|
||||||
i18next:
|
i18next:
|
||||||
specifier: ^23.16.8
|
specifier: ^23.16.8
|
||||||
version: 23.16.8
|
version: 23.16.8
|
||||||
|
@ -1138,6 +1141,10 @@ packages:
|
||||||
fastq@1.18.0:
|
fastq@1.18.0:
|
||||||
resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==}
|
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:
|
file-entry-cache@8.0.0:
|
||||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
|
@ -1845,6 +1852,9 @@ packages:
|
||||||
run-parallel@1.2.0:
|
run-parallel@1.2.0:
|
||||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||||
|
|
||||||
|
sax@1.4.1:
|
||||||
|
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
|
||||||
|
|
||||||
scheduler@0.23.2:
|
scheduler@0.23.2:
|
||||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||||
|
|
||||||
|
@ -2039,6 +2049,10 @@ packages:
|
||||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
xml-js@1.6.11:
|
||||||
|
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
yallist@3.1.1:
|
yallist@3.1.1:
|
||||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||||
|
|
||||||
|
@ -3103,6 +3117,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
reusify: 1.0.4
|
reusify: 1.0.4
|
||||||
|
|
||||||
|
feed@4.2.2:
|
||||||
|
dependencies:
|
||||||
|
xml-js: 1.6.11
|
||||||
|
|
||||||
file-entry-cache@8.0.0:
|
file-entry-cache@8.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
flat-cache: 4.0.1
|
flat-cache: 4.0.1
|
||||||
|
@ -3857,6 +3875,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask: 1.2.3
|
queue-microtask: 1.2.3
|
||||||
|
|
||||||
|
sax@1.4.1: {}
|
||||||
|
|
||||||
scheduler@0.23.2:
|
scheduler@0.23.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
|
@ -4038,6 +4058,10 @@ snapshots:
|
||||||
string-width: 5.1.2
|
string-width: 5.1.2
|
||||||
strip-ansi: 7.1.0
|
strip-ansi: 7.1.0
|
||||||
|
|
||||||
|
xml-js@1.6.11:
|
||||||
|
dependencies:
|
||||||
|
sax: 1.4.1
|
||||||
|
|
||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|
||||||
yaml@2.7.0: {}
|
yaml@2.7.0: {}
|
||||||
|
|
103
scripts/generate-rss.ts
Normal file
103
scripts/generate-rss.ts
Normal file
|
@ -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();
|
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
import { ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react';
|
import { ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react';
|
||||||
|
import { RssIcon } from 'lucide-react'; // Add this line
|
||||||
import { Update, useUpdates, getUpdateUrl } from '../utils/updates';
|
import { Update, useUpdates, getUpdateUrl } from '../utils/updates';
|
||||||
import TagFilter from './ui/TagFilter';
|
import TagFilter from './ui/TagFilter';
|
||||||
|
|
||||||
|
@ -60,6 +61,9 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||||
|
|
||||||
const Timeline = () => {
|
const Timeline = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
|
const LANGUAGE_CODE_MAP: Record<string, string> = {
|
||||||
|
'zh-TW': 'zh-Hant'
|
||||||
|
};
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
// 获取当前页码和标签筛选
|
// 获取当前页码和标签筛选
|
||||||
|
@ -118,9 +122,20 @@ const Timeline = () => {
|
||||||
<div className="container mx-auto px-8 md:px-20 lg:px-32 xl:px-48 2xl:px-64">
|
<div className="container mx-auto px-8 md:px-20 lg:px-32 xl:px-48 2xl:px-64">
|
||||||
<div className="max-w-5xl">
|
<div className="max-w-5xl">
|
||||||
<div className="flex flex-col md:flex-row md:items-center justify-between mb-12 pl-16">
|
<div className="flex flex-col md:flex-row md:items-center justify-between mb-12 pl-16">
|
||||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4 md:mb-0">
|
<div className="flex items-center gap-2">
|
||||||
|
<h2 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||||
{t('updates.title')}
|
{t('updates.title')}
|
||||||
</h2>
|
</h2>
|
||||||
|
<a
|
||||||
|
href={`/${LANGUAGE_CODE_MAP[i18n.language] || i18n.language}/rss.xml`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors"
|
||||||
|
title="RSS Feed"
|
||||||
|
>
|
||||||
|
<RssIcon size={20} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<TagFilter
|
<TagFilter
|
||||||
availableTags={availableTags}
|
availableTags={availableTags}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue