diff --git a/.gitignore b/.gitignore
index 909642c..a430c02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,11 +6,13 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
+*.tsbuildinfo
node_modules
dist
dist-ssr
*.local
+build
# Editor directories and files
.vscode/*
diff --git a/package.json b/package.json
index 6694378..e5ced4c 100644
--- a/package.json
+++ b/package.json
@@ -25,8 +25,8 @@
},
"devDependencies": {
"@eslint/js": "^9.17.0",
- "@types/react": "^18.3.18",
- "@types/react-dom": "^18.3.5",
+ "@types/react": "^19.0.2",
+ "@types/react-dom": "^19.0.2",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.17.0",
"eslint-plugin-react-hooks": "^5.0.0",
diff --git a/src/About.tsx b/src/About.tsx
index c5edd50..30f2a15 100644
--- a/src/About.tsx
+++ b/src/About.tsx
@@ -1,32 +1,12 @@
import { FC } from 'react';
import { Accordion, Card, Container, Divider, Icon } from 'semantic-ui-react';
import { isMobile } from 'react-device-detect';
+import { DonateInfo, Contributor, FAQ } from './types';
import contributors from './data/processed/contributors.json';
import faqs from './data/processed/faqs.json';
import donateInfo from './data/processed/donateInfo.json';
-interface Contributor {
- header: string;
- href: string;
- as: string;
- image: string;
- meta: string;
- description: string;
-}
-
-interface FAQ {
- key: string;
- title: string;
- content: string;
-}
-
-interface DonateInfo {
- image: string;
- header: string;
- meta: string;
-}
-
const About: FC = () => {
return (
diff --git a/src/Files.tsx b/src/Files.tsx
index 450d994..c84b282 100644
--- a/src/Files.tsx
+++ b/src/Files.tsx
@@ -1,13 +1,11 @@
import { FC, useCallback, useEffect, useRef, useReducer, useState } from 'react';
-import { Icon, Label, Pagination, Popup, Search, Table } from 'semantic-ui-react';
+import { Icon, Label, Pagination, Popup, Search, Table, SearchProps } from 'semantic-ui-react';
import * as _ from 'lodash';
-import { File, SearchState } from './types';
+import { File, SearchState, FileHash } from './types';
import files from './data/processed/files.json';
interface HashPopupProps {
- hashObj: {
- [key: string]: string;
- };
+ hashObj: FileHash;
}
const HashPopup: FC
= ({ hashObj }) => {
@@ -20,9 +18,9 @@ const HashPopup: FC = ({ hashObj }) => {
hideOnScroll
position='bottom right'
>
- {Object.keys(hashObj).map((hashType, index) => (
+ {Object.keys(hashObj || {}).map((hashType, index) => (
- {hashType}: {hashObj[hashType]}
+ {hashType}: {hashObj[hashType as keyof FileHash]}
))}
@@ -38,7 +36,7 @@ const Files: FC = () => {
});
const filesTot = shownFiles.length;
- const pagesTot = Math.floor(filesTot / pageLength) + (filesTot % pageLength !== 0);
+ const pagesTot = Math.floor(filesTot / pageLength) + (filesTot % pageLength !== 0 ? 1 : 0);
const leftNum = (activePage - 1) * pageLength;
shownFiles.forEach((item, index) => {
@@ -70,20 +68,20 @@ const Files: FC = () => {
const [searchState, searchDispatch] = useReducer(searchReducer, searchInitState);
const { loading, searchResults, value } = searchState;
- const timeoutRef = useRef();
+ const timeoutRef = useRef(undefined);
- const search = useCallback((e: any, data: { value: string }) => {
+ const search = useCallback((_e: React.MouseEvent, data: SearchProps) => {
clearTimeout(timeoutRef.current);
- searchDispatch({ type: 'SEARCH_START', query: data.value });
+ searchDispatch({ type: 'SEARCH_START', query: data.value || '' });
timeoutRef.current = setTimeout(() => {
- if (data.value.length === 0) {
+ if (!data.value || data.value.length === 0) {
searchDispatch({ type: 'SEARCH_CLEAN' });
return;
}
const isMatch = (item: File) => {
- const re = new RegExp(_.escapeRegExp(data.value), 'i');
+ const re = new RegExp(_.escapeRegExp(data.value || ''), 'i');
return re.test(item.filename);
};
@@ -101,7 +99,9 @@ const Files: FC = () => {
useEffect(() => {
return () => {
- clearTimeout(timeoutRef.current);
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
};
}, []);
@@ -119,7 +119,7 @@ const Files: FC = () => {
onSearchChange={search}
results={searchResults}
value={value}
- onResultSelect={(e, data) => {
+ onResultSelect={(_e, data) => {
searchDispatch({ type: 'SEARCH_UPDATE_SELECTION', selection: data.result });
}}
/>
@@ -131,12 +131,11 @@ const Files: FC = () => {
{shownFiles.slice(leftNum, leftNum + pageLength).map((item, index) => (
-
+
{item.filename}
@@ -144,14 +143,14 @@ const Files: FC = () => {
{Object.keys(item.tags).map((tag) => {
- switch (tag) {
- case 'hash':
- return ;
- case 'id':
- return null;
- default:
- return ;
+ if (tag === 'hash') {
+ return ;
+ }
+ if (tag === 'id') {
+ return null;
}
+ const value = item.tags[tag as keyof typeof item.tags];
+ return ;
})}
@@ -164,7 +163,7 @@ const Files: FC = () => {
{
+ onPageChange={(_e, data) => {
setActivePage(data.activePage as number);
}}
/>
diff --git a/src/Header.tsx b/src/Header.tsx
index 933b534..e952411 100644
--- a/src/Header.tsx
+++ b/src/Header.tsx
@@ -1,7 +1,6 @@
import { FC, useState, useEffect } from 'react';
import { Container, Dropdown, Menu } from 'semantic-ui-react';
import { useLocation } from 'react-router-dom';
-import { Link } from './types';
import links from './data/processed/links.json';
const Header: FC = () => {
diff --git a/src/Home.tsx b/src/Home.tsx
index 7944274..b8d7795 100644
--- a/src/Home.tsx
+++ b/src/Home.tsx
@@ -1,10 +1,11 @@
import { FC, useEffect, useReducer } from 'react';
import { EventEmitter } from 'events';
import { Container, Divider, Grid, Header, Icon, List, Modal, Segment } from 'semantic-ui-react';
-import { Software, HomeModalState } from './types';
+import { Software, HomeModalState, FileSource } from './types';
import softwareData from './data/processed/software.json';
-const softwareSlug = (softwareData as Software[]).map((item) => item.slug);
+const typedSoftwareData = (softwareData as unknown) as Software[];
+const softwareSlug = typedSoftwareData.map((item) => item.slug);
const eventEmitter = new EventEmitter();
interface SoftwareCardProps {
@@ -35,7 +36,7 @@ const SoftwareList: FC = () => {
return (
- {(softwareData as Software[])
+ {typedSoftwareData
.filter((item) => !item.recommend)
.map((item) => (
@@ -48,12 +49,14 @@ const SoftwareList: FC = () => {
const HomeModal: FC = () => {
const modalReducer = (state: HomeModalState, action: { type: string; slug?: string }) => {
switch (action.type) {
- case 'open':
+ case 'open': {
+ const software = typedSoftwareData[softwareSlug.indexOf(action.slug || '')];
return {
...state,
open: true,
- download: softwareData[softwareSlug.indexOf(action.slug || '')].sources
+ download: software ? software.sources : {}
};
+ }
case 'close':
default:
return { ...state, open: false, download: {} };
@@ -63,114 +66,88 @@ const HomeModal: FC = () => {
const [state, dispatch] = useReducer(modalReducer, { open: false, download: {} });
useEffect(() => {
- const listener = eventEmitter.addListener('openDownloadModal', (data) => {
+ const handler = (data: { type: string; slug?: string }) => {
dispatch(data);
- });
+ };
+ eventEmitter.addListener('openDownloadModal', handler);
return () => {
- listener.remove();
+ eventEmitter.removeListener('openDownloadModal', handler);
};
}, []);
return (
- dispatch({type: 'close'})}
- >
- 选择下载地址
-
-
-
- {
- Object.keys(state.download).map((key, index) => {
- return (
-
- {key}
-
- {
- state.download[key].map((item, index) => {
- return (
-
- {item.filename}
-
- );
- })
- }
-
-
- );
- })
- }
-
-
-
-
+ dispatch({type: 'close'})}
+ >
+ 选择下载地址
+
+
+
+ {Object.entries(state.download).map(([key, sources], index) => (
+
+ {key}
+
+ {(sources as FileSource[]).map((item, itemIndex) => (
+
+ {item.filename}
+
+ ))}
+
+
+ ))}
+
+
+
+
);
};
const Home: FC = () => {
return (
-
-
-
-
-
- 欢迎
-
- 这是一个搜集常用软件镜像下载地址的列表(仅针对中国大陆用户)
-
-
-
- {/*
-
-
-
-
- 需要帮助
-
-
- 提交
- 建议&问题
- 提交 Github
- Issues
- (国内可能无法正常打开)
- 查阅 FAQ
-
-
-
- */}
-
-
-
-
-
-
-
- {
- softwareData.filter((item) => {
- return item.recommend === true;
- }).map((item) => {
- return ();
- })
- }
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ 欢迎
+
+ 这是一个搜集常用软件镜像下载地址的列表(仅针对中国大陆用户)
+
+
+
+
+
+
+
+
+
+
+ {typedSoftwareData
+ .filter((item) => item.recommend)
+ .map((item) => (
+
+ ))
+ }
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/main.tsx b/src/main.tsx
index 94baa60..2721e6d 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import ReactDOM from 'react-dom';
+import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';
import 'fomantic-ui-css/semantic.min.css';
@@ -45,11 +45,11 @@ const App: React.FC = () => {
);
};
-ReactDOM.render(
+const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
+root.render(
- ,
- document.getElementById('root')
+
);
reportWebVitals((metric) => console.log(metric));
\ No newline at end of file
diff --git a/src/types/index.ts b/src/types/index.ts
index f6386c7..c90a007 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,3 +1,29 @@
+export interface FileSource {
+ filename: string;
+ url: string;
+}
+
+export interface FileHash {
+ md5?: string;
+ sha1?: string;
+ sha256?: string;
+}
+
+export interface FileTags {
+ source: string;
+ id: string;
+ filetype: string;
+ hash?: FileHash;
+}
+
+export interface File {
+ filename: string;
+ url: string;
+ urlType: "directly" | "multiple";
+ tags: FileTags;
+ index?: number;
+}
+
export interface Software {
name: string;
website: string;
@@ -6,28 +32,8 @@ export interface Software {
recommend: boolean;
slug: string;
sources: {
- [key: string]: {
- filename: string;
- url: string;
- }[];
- };
-}
-
-export interface File {
- filename: string;
- url: string;
- urlType: string;
- tags: {
- source: string;
- id: string;
- filetype: string;
- hash?: {
- md5: string;
- sha1: string;
- sha256: string;
- };
+ [key: string]: FileSource[];
};
- index?: number;
}
export interface Contributor {
@@ -57,44 +63,6 @@ export interface BuildInfo {
time: string;
}
-export interface Software {
- name: string;
- website: string;
- description: string;
- filesId: string[];
- recommend: boolean;
- slug: string;
- sources: {
- [key: string]: FileSource[];
- };
-}
-
-export interface FileSource {
- filename: string;
- url: string;
-}
-
-export interface FileHash {
- md5?: string;
- sha1?: string;
- sha256?: string;
-}
-
-export interface FileTags {
- source: string;
- id: string;
- filetype: string;
- hash?: FileHash;
-}
-
-export interface File {
- filename: string;
- url: string;
- urlType: "directly" | "multiple";
- tags: FileTags;
- index?: number;
-}
-
export interface SearchState {
loading: boolean;
searchResults: SearchResult[];
@@ -113,4 +81,11 @@ export interface HomeModalState {
download: {
[key: string]: FileSource[];
};
+}
+
+export interface DonateInfo {
+ header: string;
+ as: string;
+ image: string;
+ meta: string;
}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 89d2973..33b04a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -624,22 +624,16 @@
dependencies:
undici-types "~6.20.0"
-"@types/prop-types@*":
- version "15.7.14"
- resolved "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2"
- integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==
-
-"@types/react-dom@^18.3.5":
- version "18.3.5"
- resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.5.tgz#45f9f87398c5dcea085b715c58ddcf1faf65f716"
- integrity sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==
-
-"@types/react@^18.3.18":
- version "18.3.18"
- resolved "https://registry.npmmirror.com/@types/react/-/react-18.3.18.tgz#9b382c4cd32e13e463f97df07c2ee3bbcd26904b"
- integrity sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==
- dependencies:
- "@types/prop-types" "*"
+"@types/react-dom@^19.0.2":
+ version "19.0.2"
+ resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.0.2.tgz#ad21f9a1ee881817995fd3f7fd33659c87e7b1b7"
+ integrity sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==
+
+"@types/react@^19.0.2":
+ version "19.0.2"
+ resolved "https://registry.npmmirror.com/@types/react/-/react-19.0.2.tgz#9363e6b3ef898c471cb182dd269decc4afc1b4f6"
+ integrity sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==
+ dependencies:
csstype "^3.0.2"
"@typescript-eslint/eslint-plugin@8.18.2":