parent
e30d41db2f
commit
4ba16a7ddd
4 changed files with 147 additions and 4 deletions
|
@ -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",
|
||||
|
|
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
|
@ -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: {}
|
||||
|
|
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 { 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<PaginationProps> = ({
|
|||
|
||||
const Timeline = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const LANGUAGE_CODE_MAP: Record<string, string> = {
|
||||
'zh-TW': 'zh-Hant'
|
||||
};
|
||||
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="max-w-5xl">
|
||||
<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')}
|
||||
</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
|
||||
availableTags={availableTags}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue