合并浙大上游仓库

This commit is contained in:
zyh001
2024-05-15 18:46:34 +08:00
parent 2e623948dd
commit 95706fa4f2
30 changed files with 508 additions and 184 deletions

View File

@@ -13,13 +13,15 @@ module.exports = {
], ],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: './',
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true,
}, },
ecmaVersion: 'latest', ecmaVersion: 'latest',
sourceType: 'module', sourceType: 'module',
}, },
plugins: ['react', '@typescript-eslint', 'prettier'], plugins: ['react', '@typescript-eslint', 'import', 'prettier'],
rules: { rules: {
// needed by prettier // needed by prettier
'prettier/prettier': 'warn', 'prettier/prettier': 'warn',

View File

@@ -16,7 +16,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [18.x] node-version: [20.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps: steps:

View File

@@ -1,4 +1,6 @@
image: node:lts-bullseye image:
name: node:20
pull_policy: if-not-present
stages: stages:
- build - build
@@ -8,12 +10,16 @@ gatsby-build:
stage: build stage: build
variables: variables:
CHOKIDAR_USEPOLLING: 1 CHOKIDAR_USEPOLLING: 1
GATSBY_TELEMETRY_DISABLED: 1
cache: cache:
paths: paths:
- node_modules/ - .npm/
script: script:
- bash cached-restore.sh - apt-get update && apt-get install -y libvips42 libvips-dev
- yarn run build - npm config set registry https://registry.npmmirror.com
- npm install -g npm@10.5.1
- npm ci --cache .npm --prefer-offline
- npm run build
artifacts: artifacts:
paths: paths:
- public - public

View File

@@ -1 +1,5 @@
import './src/styles/global.scss'; import './src/styles/global.scss';
import React from 'react';
import RootLayout from './src/utils/root-layout';
export const wrapRootElement = ({ element }) => <RootLayout>{element}</RootLayout>

View File

@@ -16,20 +16,6 @@ module.exports = {
assetPrefix: config.assetPrefix, assetPrefix: config.assetPrefix,
pathPrefix: config.pathPrefix, pathPrefix: config.pathPrefix,
plugins: [ plugins: [
{
resolve: `gatsby-theme-material-ui`,
options: {
webFontsConfig: {
fonts: {
google: [
{
family: `Metrophobic`
},
],
},
},
}
},
{ {
resolve: 'gatsby-source-filesystem', resolve: 'gatsby-source-filesystem',
options: { options: {
@@ -57,7 +43,7 @@ module.exports = {
mdxOptions: { mdxOptions: {
remarkPlugins: [ remarkPlugins: [
// Add GitHub Flavored Markdown (GFM) support // Add GitHub Flavored Markdown (GFM) support
// Warning: Most of the remark ecosystem is ESM which means that packages // Warning: Most of the remark ecosystem is ESM which means that packages
// like remark-gfm dont work out of the box with Gatsby v5. To make remark-gfm // like remark-gfm dont work out of the box with Gatsby v5. To make remark-gfm
// compatible with the current Gatsby version, we have to use an old version of // compatible with the current Gatsby version, we have to use an old version of
// remark-gfm. Please take care in future updates. // remark-gfm. Please take care in future updates.
@@ -72,11 +58,11 @@ module.exports = {
rule: { rule: {
include: /icons/, include: /icons/,
omitKeys: [ omitKeys: [
'inkscapePageshadow', 'inkscapePageopacity', 'inkscapePagecheckerboard', 'inkscapePageshadow', 'inkscapePageopacity', 'inkscapePagecheckerboard',
'inkscapeZoom', 'inkscapeCx', 'inkscapeCy', 'inkscapeWindowWidth', 'inkscapeWindowHeight', 'inkscapeZoom', 'inkscapeCx', 'inkscapeCy', 'inkscapeWindowWidth', 'inkscapeWindowHeight',
'inkscapeWindowX', 'inkscapeWindowY', 'inkscapeWindowMaximized', 'inkscapeCurrentLayer', 'inkscapeWindowX', 'inkscapeWindowY', 'inkscapeWindowMaximized', 'inkscapeCurrentLayer',
'sodipodiNodetypes', 'sodipodiDocname', 'inkscapeVersion', 'xmlnsInkscape', 'xmlnsSodipodi', 'sodipodiNodetypes', 'sodipodiDocname', 'inkscapeVersion', 'xmlnsInkscape', 'xmlnsSodipodi',
'xmlnsSvg', 'xmlnsSvg',
] ]
} }
} }
@@ -113,7 +99,6 @@ module.exports = {
icon: 'resource/icons/favicon.svg', icon: 'resource/icons/favicon.svg',
}, },
}, },
`gatsby-plugin-preact`,
`gatsby-plugin-sass`, `gatsby-plugin-sass`,
{ {
resolve: "gatsby-plugin-sitemap", resolve: "gatsby-plugin-sitemap",

View File

@@ -2,6 +2,7 @@ const { match, compile } = require("path-to-regexp");
const { createFilePath } = require(`gatsby-source-filesystem`) const { createFilePath } = require(`gatsby-source-filesystem`)
const { createContentDigest } = require(`gatsby-core-utils`) const { createContentDigest } = require(`gatsby-core-utils`)
const config = require('./config'); const config = require('./config');
const path = require("path");
const mdxResolverPassthrough = (fieldName) => async ( const mdxResolverPassthrough = (fieldName) => async (
source, source,
@@ -194,3 +195,13 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
}); });
}) })
} }
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
resolve: {
alias: {
'~': path.resolve(__dirname, 'src'),
},
},
});
};

5
gatsby-ssr.js Normal file
View File

@@ -0,0 +1,5 @@
import './src/styles/global.scss';
import React from 'react';
import RootLayout from './src/utils/root-layout';
export const wrapRootElement = ({ element }) => <RootLayout>{element}</RootLayout>

View File

@@ -17,67 +17,66 @@
"lint-check": "eslint src/**/*" "lint-check": "eslint src/**/*"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.9.0", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.8.1", "@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.5",
"@fontsource/poppins": "^4.5.10", "@fontsource/poppins": "^4.5.10",
"@formatjs/intl-displaynames": "^5.4.3", "@formatjs/intl-displaynames": "^5.4.3",
"@iconify/icons-file-icons": "^1.2.3", "@iconify/icons-file-icons": "^1.2.3",
"@iconify/icons-logos": "^1.2.36", "@iconify/icons-logos": "^1.2.36",
"@iconify/icons-mdi": "^1.2.44", "@iconify/icons-mdi": "^1.2.48",
"@iconify/icons-simple-icons": "^1.2.74", "@iconify/icons-simple-icons": "^1.2.74",
"@iconify/react": "^3.2.1", "@iconify/react": "^3.2.2",
"@mdx-js/mdx": "^3.0.1",
"@mdx-js/react": "^2.3.0", "@mdx-js/react": "^2.3.0",
"@mui/icons-material": "^5.6.2", "@mui/icons-material": "^5.15.15",
"@mui/material": "^5.6.3", "@mui/material": "^5.15.15",
"gatsby": "^5.9.1", "gatsby": "^5.13.3",
"gatsby-plugin-manifest": "^5.6.0", "gatsby-plugin-manifest": "^5.13.1",
"gatsby-plugin-mdx": "^5.6.0", "gatsby-plugin-mdx": "^5.13.1",
"gatsby-plugin-preact": "^7.6.1", "gatsby-plugin-react-helmet": "^6.13.1",
"gatsby-plugin-react-i18next": "^3.0.1", "gatsby-plugin-react-i18next": "^3.0.1",
"gatsby-plugin-react-svg": "^3.1.0", "gatsby-plugin-react-svg": "^3.3.0",
"gatsby-plugin-sass": "^6.6.0", "gatsby-plugin-sass": "^6.13.1",
"gatsby-plugin-sitemap": "^6.7.0", "gatsby-plugin-sitemap": "^6.13.1",
"gatsby-source-filesystem": "^5.6.0", "gatsby-source-filesystem": "^5.13.1",
"gatsby-theme-material-ui": "^5.2.0", "i18next": "^22.5.1",
"gatsby-theme-material-ui-top-layout": "^5.2.0", "js-search": "^2.0.1",
"i18next": "^21.6.16",
"js-search": "^2.0.0",
"natsort": "^2.0.3", "natsort": "^2.0.3",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.2",
"preact": "^10.12.1", "prism-react-renderer": "^1.3.5",
"preact-render-to-string": "^5.2.6", "react": "^18.2.0",
"prism-react-renderer": "^1.3.1", "react-dom": "^18.2.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-i18next": "^11.16.7", "react-i18next": "^12.3.1",
"remark-gfm": "^1", "remark-gfm": "^1.0.0",
"sass": "^1.53.0" "sass": "^1.74.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.17.10", "@babel/cli": "^7.24.1",
"@babel/plugin-transform-typescript": "^7.16.8", "@babel/plugin-transform-typescript": "^7.24.4",
"@types/js-search": "^1.4.0", "@types/js-search": "^1.4.4",
"@types/mdx": "^2.0.1", "@types/mdx": "^2.0.12",
"@types/mdx-js__react": "^1.5.5", "@types/mdx-js__react": "^1.5.8",
"@types/node": "^18.0.0", "@types/node": "^18.19.31",
"@types/react": "^18.0.14", "@types/react": "^18.2.75",
"@types/react-dom": "^18.0.5", "@types/react-dom": "^18.2.24",
"@typescript-eslint/eslint-plugin": "^5.28.0", "@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.28.0", "@typescript-eslint/parser": "^5.62.0",
"babel-plugin-i18next-extract": "^0.8.3", "babel-plugin-i18next-extract": "^0.8.3",
"eslint": "^8.2.0", "eslint": "^8.57.0",
"eslint-config-airbnb": "19.0.4", "eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.10.0",
"eslint-import-resolver-typescript": "^3.2.1", "eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.25.3", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.30.0", "eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-react-hooks": "^4.6.0",
"http-proxy-middleware": "^2.0.6", "http-proxy-middleware": "^2.0.6",
"prettier": "2.7.1", "prettier": "^3.2.5",
"typescript": "^4.7.3" "prettier-eslint": "^16.3.0",
"typescript": "^5.4.5"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -38,13 +38,18 @@ export default ({ children, codeStyle, language }: CodeBlockProps) => {
...codeStyle, ...codeStyle,
}} }}
> >
{tokens.map((line, i) => ( {tokens.map((line, i) => {
<div key={i} {...getLineProps({ line, key: i })}> if (line.length === 1 && line[0].content.indexOf('\u200b') > -1) {
{line.map((token, key) => ( return <br />;
<span key={key} {...getTokenProps({ token, key })} /> }
))} return (
</div> <div key={i} {...getLineProps({ line, key: i })}>
))} {line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</div>
);
})}
</code> </code>
)} )}
</Highlight> </Highlight>

View File

@@ -1,7 +1,7 @@
import { PaletteMode, ThemeOptions } from '@mui/material'; import { PaletteMode, ThemeOptions, createTheme } from '@mui/material';
import '@fontsource/poppins'; import '@fontsource/poppins';
export default function configTheme(mode: PaletteMode): ThemeOptions { function configTheme(mode: PaletteMode): ThemeOptions {
return { return {
palette: palette:
mode === 'light' mode === 'light'
@@ -91,6 +91,24 @@ export default function configTheme(mode: PaletteMode): ThemeOptions {
}; };
} }
export const lightTheme = createTheme(
{
palette: {
mode: 'light',
},
},
configTheme('light')
);
export const darkTheme = createTheme(
{
palette: {
mode: 'dark',
},
},
configTheme('dark')
);
/* the code below decares customized color */ /* the code below decares customized color */
declare module '@mui/material/styles' { declare module '@mui/material/styles' {

View File

@@ -1,8 +1,10 @@
import React from 'react'; import React from 'react';
import { Card, Grid, Typography } from '@mui/material'; import Card from '@mui/material/Card';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import AlbumIcon from '@mui/icons-material/Album'; import AlbumIcon from '@mui/icons-material/Album';
import { Trans } from 'gatsby-plugin-react-i18next'; import { Trans } from 'gatsby-plugin-react-i18next';
import { CardActionArea } from 'gatsby-theme-material-ui'; import { LinkCardActionArea as CardActionArea } from './link-mui-components';
export interface FileProps { export interface FileProps {
name: string; name: string;

View File

@@ -1,6 +1,10 @@
import { Box, Card, CardContent, Grid, Typography } from '@mui/material'; import Box from '@mui/material/Box';
import { CardActionArea } from 'gatsby-theme-material-ui'; import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import * as React from 'react'; import * as React from 'react';
import { LinkCardActionArea as CardActionArea } from './link-mui-components';
export type mirrorBrief = { export type mirrorBrief = {
name: string; name: string;

View File

@@ -0,0 +1,39 @@
import React, { ForwardedRef } from 'react';
import Button from '@mui/material/Button';
import CardActionArea from '@mui/material/CardActionArea';
import Link from '@mui/material/Link';
import IconButton from '@mui/material/IconButton';
import { navigate } from 'gatsby';
import { ButtonBaseProps } from '@mui/material';
interface LinkProps {
to?: string;
}
function linkifyComponent<T, PropType>(
ButtonBaseComponent: React.ComponentType<ButtonBaseProps & PropType>
) {
const f = (
props: ButtonBaseProps & PropType & LinkProps,
ref: ForwardedRef<T>
) => {
const propOnClick = props.onClick;
const clickHandler: React.MouseEventHandler<HTMLButtonElement> = event => {
if (propOnClick) propOnClick(event);
if (props.to) navigate(props.to);
};
return (
<ButtonBaseComponent {...props} onClick={clickHandler} ref={ref}>
{props.children}
</ButtonBaseComponent>
);
};
return React.forwardRef(f);
}
const LinkButton = linkifyComponent(Button);
const LinkCardActionArea = linkifyComponent(CardActionArea);
const LinkIconButton = linkifyComponent(IconButton);
const LinkLink = linkifyComponent(Link);
export { LinkProps, LinkButton, LinkCardActionArea, LinkIconButton, LinkLink };

View File

@@ -1,9 +1,9 @@
import InfoIcon from '@mui/icons-material/Info'; import InfoIcon from '@mui/icons-material/Info';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import { Tooltip } from '@mui/material'; import Tooltip from '@mui/material/Tooltip';
import { IconButton } from 'gatsby-theme-material-ui';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import * as React from 'react'; import * as React from 'react';
import { LinkIconButton as IconButton } from './link-mui-components';
import { usePrefs } from './preferences-context'; import { usePrefs } from './preferences-context';
const nameMode = { const nameMode = {

View File

@@ -1,12 +1,15 @@
import { Card, Grid, Typography, Tooltip } from '@mui/material'; import Card from '@mui/material/Card';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import Tooltip from '@mui/material/Tooltip';
import { useI18next } from 'gatsby-plugin-react-i18next'; import { useI18next } from 'gatsby-plugin-react-i18next';
import { CardActionArea } from 'gatsby-theme-material-ui';
import * as React from 'react'; import * as React from 'react';
import VerifiedIcon from '@mui/icons-material/Verified';
import { LinkCardActionArea as CardActionArea } from './link-mui-components';
import { Locale, Mirror } from '../types/mirror'; import { Locale, Mirror } from '../types/mirror';
import StatusIndicator from './status-indicator'; import StatusIndicator from './status-indicator';
import { getUrl } from '../utils/url'; import { getUrl } from '../utils/url';
import { usePrefs } from './preferences-context'; import { usePrefs } from './preferences-context';
import VerifiedIcon from '@mui/icons-material/Verified';
import officialCertificatedMirrorList from '../utils/official-certificated-mirror-list'; import officialCertificatedMirrorList from '../utils/official-certificated-mirror-list';
const SearchItemCard = (props: { queryItem: Mirror }) => { const SearchItemCard = (props: { queryItem: Mirror }) => {

View File

@@ -1,10 +1,9 @@
import React from 'react'; import React from 'react';
import { JSX } from 'preact';
import { useStaticQuery, graphql } from 'gatsby'; import { useStaticQuery, graphql } from 'gatsby';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { useI18next } from 'gatsby-plugin-react-i18next'; import { useI18next } from 'gatsby-plugin-react-i18next';
type MetaProps = JSX.IntrinsicElements['meta']; type MetaProps = React.JSX.IntrinsicElements['meta'];
const Helmet: React.FC = ({children, title, meta}) => { const Helmet: React.FC = ({children, title, meta}) => {
const {languages, language, originalPath, defaultLanguage, siteUrl = ''} = useI18next(); const {languages, language, originalPath, defaultLanguage, siteUrl = ''} = useI18next();
@@ -16,11 +15,7 @@ const Helmet: React.FC = ({children, title, meta}) => {
<> <>
<html lang={language} /> <html lang={language} />
<title>{title}</title> <title>{title}</title>
{ {meta?.map((m: MetaProps, i: number) => <meta {...m} key={i}/>)}
meta?.map((m: MetaProps) => (
<meta {...m} />
))
}
<link rel="canonical" href={createUrlWithLang(language)} /> <link rel="canonical" href={createUrlWithLang(language)} />
{languages.map((lng) => ( {languages.map((lng) => (
<link rel="alternate" key={lng} href={createUrlWithLang(lng)} hrefLang={lng} /> <link rel="alternate" key={lng} href={createUrlWithLang(lng)} hrefLang={lng} />

View File

@@ -5,14 +5,14 @@ import {
} from '@mui/material/styles'; } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery'; import useMediaQuery from '@mui/material/useMediaQuery';
import * as React from 'react'; import * as React from 'react';
import configTheme from './config-theme'; import { lightTheme, darkTheme } from './config-theme';
import { readCache, writeCache } from '../utils/cache'; import { readCache, writeCache } from '../utils/cache';
export type ThemeMode = PaletteMode | 'auto'; export type ThemeMode = PaletteMode | 'auto';
export declare type ThemeContextInterface = [ export declare type ThemeContextInterface = [
ThemeMode, ThemeMode,
(mode: ThemeMode) => void (mode: ThemeMode) => void,
]; ];
export const ThemeContext = React.createContext<ThemeContextInterface | null>( export const ThemeContext = React.createContext<ThemeContextInterface | null>(
@@ -27,11 +27,12 @@ export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [mode, setMode] = React.useState<ThemeMode>(savedMode); const [mode, setMode] = React.useState<ThemeMode>(savedMode);
let paletteMode: PaletteMode; let paletteMode: PaletteMode;
// ensure renders execute equal number of hooks
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
const prefersLight = useMediaQuery('(prefers-color-scheme: light)');
if (mode === 'auto') { if (mode === 'auto') {
// wait for media query result stable // wait for media query result stable
// ref: https://github.com/mui/material-ui/issues/15588#issuecomment-567803082 // ref: https://github.com/mui/material-ui/issues/15588#issuecomment-567803082
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
const prefersLight = useMediaQuery('(prefers-color-scheme: light)');
if (prefersDark !== !prefersLight) return null; if (prefersDark !== !prefersLight) return null;
const preferredMode = prefersDark ? 'dark' : 'light'; const preferredMode = prefersDark ? 'dark' : 'light';
paletteMode = preferredMode; paletteMode = preferredMode;
@@ -39,18 +40,7 @@ export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
paletteMode = mode; paletteMode = mode;
} }
const theme = React.useMemo( const theme = paletteMode === 'light' ? lightTheme : darkTheme;
() =>
createTheme(
{
palette: {
mode: paletteMode,
},
},
configTheme(paletteMode)
),
[paletteMode]
);
const updateMode = (newMode: ThemeMode) => { const updateMode = (newMode: ThemeMode) => {
setMode(newMode); setMode(newMode);

View File

@@ -2,9 +2,9 @@ import Brightness4Icon from '@mui/icons-material/Brightness4';
import BrightnessAutoIcon from '@mui/icons-material/BrightnessAuto'; import BrightnessAutoIcon from '@mui/icons-material/BrightnessAuto';
import BrightnessHighIcon from '@mui/icons-material/BrightnessHigh'; import BrightnessHighIcon from '@mui/icons-material/BrightnessHigh';
import ToolTip from '@mui/material/Tooltip'; import ToolTip from '@mui/material/Tooltip';
import { IconButton } from 'gatsby-theme-material-ui';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LinkIconButton as IconButton } from './link-mui-components';
import { ThemeMode, useMode } from './theme-context'; import { ThemeMode, useMode } from './theme-context';
export default () => { export default () => {

View File

@@ -0,0 +1,236 @@
import React, { useState } from 'react';
import {
FormControl,
InputLabel,
Select,
MenuItem,
Box,
Grid,
Typography,
FormControlLabel,
Switch,
FormGroup,
} from '@mui/material';
import { Trans } from 'gatsby-plugin-react-i18next';
import CodeBlock from './code-block';
const ubuntuVersionMap: Record<number | string, string> = {
2404: 'noble',
2310: 'mantic',
2304: 'lunar',
2210: 'kinetic',
2204: 'jammy',
2004: 'focal',
1804: 'bionic',
1604: 'xenial',
1404: 'trusty',
};
const isLTSVersion = (version: number) => {
return Math.floor(version / 100) % 2 === 0 && version % 100 === 4;
};
const configGenOld = (
version: number | string,
enableHTTPS: boolean,
enableSrc: boolean,
enableProposed: boolean,
enableSecurity: boolean,
ubuntuVariant: string
) => {
const ubuntuName = ubuntuVersionMap[version];
const httpProtocol = enableHTTPS ? 'https' : 'http';
const debSrcText = enableSrc ? '' : '# ';
const proposedText = enableProposed ? '' : '# ';
const securityRepo = enableSecurity
? 'mirrors.jcut.edu.cn'
: 'security.ubuntu.com';
return `deb ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/ ${ubuntuName} main restricted universe multiverse
${debSrcText}deb-src ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/ ${ubuntuName} main restricted universe multiverse
deb ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/ ${ubuntuName}-updates main restricted universe multiverse
${debSrcText}deb-src ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/ ${ubuntuName}-updates main restricted universe multiverse
deb ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/ ${ubuntuName}-backports main restricted universe multiverse
${debSrcText}deb-src ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/ ${ubuntuName}-backports main restricted universe multiverse
deb ${httpProtocol}://${securityRepo}/${ubuntuVariant}/ ${ubuntuName}-security main restricted universe multiverse
${debSrcText}deb-src ${httpProtocol}://${securityRepo}/${ubuntuVariant}/ ${ubuntuName}-security main restricted universe multiverse
${proposedText}deb ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/ ${ubuntuName}-proposed main restricted universe multiverse
${proposedText}${debSrcText}deb-src ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/ ${ubuntuName}-proposed main restricted universe multiverse`;
};
const configGenNew = (
version: number | string,
enableHTTPS: boolean,
enableSrc: boolean,
enableProposed: boolean,
enableSecurity: boolean,
ubuntuVariant: string
) => {
const ubuntuName = ubuntuVersionMap[version];
const httpProtocol = enableHTTPS ? 'https' : 'http';
const debSrcText = enableSrc ? ' deb-src' : '';
const securityRepo = enableSecurity
? 'mirrors.zju.edu.cn'
: 'security.ubuntu.com';
let sources = `Types: deb${debSrcText}
URIs: ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/
Suites: ${ubuntuName} ${ubuntuName}-updates ${ubuntuName}-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
\u200b
Types: deb${debSrcText}
URIs: ${httpProtocol}://${securityRepo}/${ubuntuVariant}/
Suites: ${ubuntuName}-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
`;
if (enableProposed) {
sources += `\u200b
Types: deb${debSrcText}
URIs: ${httpProtocol}://mirrors.jcut.edu.cn/${ubuntuVariant}/
Suites: ${ubuntuName}-proposed
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
`;
}
return sources;
};
export default ({ ubuntuVariant }: { ubuntuVariant: string }) => {
const [version, setVersion] = useState(
Object.keys(ubuntuVersionMap)
.reverse()
.filter(v => isLTSVersion(parseInt(v, 10)))[0]
);
const [enableHTTPS, setEnableHTTPS] = useState(true);
const [enableSrc, setEnableSrc] = useState(false);
const [enableProposed, setEnableProposed] = useState(false);
const [enableSecurity, setEnableSecurity] = useState(true);
const shouldUseNewConf = () => parseInt(version, 10) >= 2404;
return (
<Box>
<Grid
container
direction="row"
justifyContent="flex-start"
alignItems="flex-end"
>
<Grid item sx={{ mb: 1 }}>
<Typography component="p"> Ubuntu </Typography>
</Grid>
<Grid item>
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
<InputLabel id="demo-simple-select-helper-label">
<Trans></Trans>
</InputLabel>
<Select
labelId="demo-simple-select-helper-label"
id="demo-simple-select-helper"
label="Age"
onChange={event => {
setVersion(event.target.value as string);
}}
defaultValue={version}
>
{Object.keys(ubuntuVersionMap)
.reverse()
.map((v: string) => {
const item = parseInt(v, 10);
let desc = (item / 100).toFixed(2);
if (isLTSVersion(item)) desc += ' LTS';
desc += ` (${ubuntuVersionMap[item]})`;
return (
<MenuItem key={item} value={item}>
{desc}
</MenuItem>
);
})}
</Select>
</FormControl>
</Grid>
</Grid>
<FormGroup>
<FormControlLabel
control={
<Switch
checked={enableHTTPS}
onChange={e => setEnableHTTPS(e.target.checked)}
/>
}
label="启用 HTTPS (容器镜像可能不支持)"
/>
<FormControlLabel
control={
<Switch
checked={enableSrc}
onChange={e => setEnableSrc(e.target.checked)}
/>
}
label="启用源码源(默认禁用以提高速度)"
/>
<FormControlLabel
control={
<Switch
checked={enableProposed}
onChange={e => setEnableProposed(e.target.checked)}
/>
}
label="启用预发布软件源(不建议启用)"
/>
<FormControlLabel
control={
<Switch
checked={enableSecurity}
onChange={e => setEnableSecurity(e.target.checked)}
/>
}
label="启用安全更新镜像(仅推荐校网内启用)"
/>
</FormGroup>
<Grid container direction="row" my={2}>
<Typography> Ubuntu </Typography>
<code
style={{ margin: '0 0.5rem', fontWeight: 'bold', color: 'brown' }}
>
{shouldUseNewConf()
? '/etc/apt/sources.list.d/ubuntu.sources'
: '/etc/apt/sources.list'}
</code>
<Typography>
使
</Typography>
{shouldUseNewConf() && (
<>
<Typography style={{ fontWeight: 'bold' }}>
Ubuntu
</Typography>
<code style={{ margin: '0 0.5rem', fontWeight: 'bold' }}>
/etc/apt/sources.list
</code>
<Typography style={{ fontWeight: 'bold' }}></Typography>
</>
)}
</Grid>
<CodeBlock language="bash">
{shouldUseNewConf()
? configGenNew(
version,
enableHTTPS,
enableSrc,
enableProposed,
enableSecurity,
ubuntuVariant
)
: configGenOld(
version,
enableHTTPS,
enableSrc,
enableProposed,
enableSecurity,
ubuntuVariant
)}
</CodeBlock>
</Box>
);
};

View File

@@ -1,24 +0,0 @@
import CssBaseline from '@mui/material/CssBaseline';
import Viewport from 'gatsby-theme-material-ui-top-layout/src/components/viewport';
import React from 'react';
import { ThemeProvider } from '../components/theme-context';
import { PrefsProvider } from '../components/preferences-context';
export default function wrapWithProvider({
element,
}: {
element: React.ReactNode;
}) {
return (
<React.StrictMode>
<Viewport />
<PrefsProvider>
<ThemeProvider>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
{element}
</ThemeProvider>
</PrefsProvider>
</React.StrictMode>
);
}

7
src/pages/404.tsx Normal file
View File

@@ -0,0 +1,7 @@
import React from 'react';
const NotFoundPage = () => {
return <h1>Page Not Found</h1>;
};
export default NotFoundPage;

View File

@@ -1,4 +1,4 @@
import { Box, Chip, Grid, Link, Typography } from '@mui/material'; import { Box, Chip, ChipProps, Grid, Link, Typography } from '@mui/material';
import { graphql } from 'gatsby'; import { graphql } from 'gatsby';
import { Trans, useI18next } from 'gatsby-plugin-react-i18next'; import { Trans, useI18next } from 'gatsby-plugin-react-i18next';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -9,7 +9,7 @@ import SearchTable from '../components/search-table';
import NavBar from '../components/nav-bar'; import NavBar from '../components/nav-bar';
import Seo from '../components/seo'; import Seo from '../components/seo';
import ThemeIconButton from '../components/theme-icon-button'; import ThemeIconButton from '../components/theme-icon-button';
import { Mirror, MirrorDto } from '../types/mirror'; import { Locale, Mirror, MirrorDto } from '../types/mirror';
import frequentlyUsedMirror from '../utils/frequently-used-mirror-list'; import frequentlyUsedMirror from '../utils/frequently-used-mirror-list';
import { getUrl } from '../utils/url'; import { getUrl } from '../utils/url';
import { readCache, writeCache } from '../utils/cache'; import { readCache, writeCache } from '../utils/cache';
@@ -40,7 +40,12 @@ interface Data {
}; };
} }
const networkMap = { const networkMap: {
[value: number]: {
text: string;
color: ChipProps['color'];
};
} = {
0: { 0: {
text: '全新版本', text: '全新版本',
color: 'primary', color: 'primary',
@@ -124,12 +129,13 @@ const Index = ({ data }: { data: Data }) => {
.filter(d => d.locale === language) .filter(d => d.locale === language)
.map( .map(
d => d =>
[d.frontmatter.title, new Date(d.frontmatter.date), d.slug] as ( [d.frontmatter.title, new Date(d.frontmatter.date), d.slug] as [
| Date string,
| string Date,
)[] string,
]
) )
.sort((a, b) => b[1] - a[1]), .sort((a, b) => b[1].getTime() - a[1].getTime()),
[data, language] [data, language]
); );
@@ -226,17 +232,24 @@ const Index = ({ data }: { data: Data }) => {
<Typography gutterBottom variant="h5" component="div"> <Typography gutterBottom variant="h5" component="div">
<Trans></Trans> <Trans></Trans>
</Typography> </Typography>
<Grid container spacing={{ xs: 2 }} columns={{ xs: 1, sm: 3, md: 6 }}> <Grid
container
spacing={{ xs: 2 }}
columns={{ xs: 1, sm: 3, md: 6 }}
>
{frequentlyUsedMirror.map((e, i) => { {frequentlyUsedMirror.map((e, i) => {
const mirror = mirrors[e.id]; const mirror = mirrors[e.id];
return ( return (
mirror && ( mirror && (
<Grid item xs={1} key={i}> <Grid item xs={1} key={i}>
<FrequentlyUsedMirrorCard <FrequentlyUsedMirrorCard
name={mirror.name[language]} name={mirror.name[language as Locale]}
desc={mirror.desc[language]} desc={mirror.desc[language as Locale]}
icon={e.icon} icon={e.icon}
url={getUrl(mirror.docUrl || mirror.url, !!mirror.docUrl)} url={getUrl(
mirror.docUrl || mirror.url,
!!mirror.docUrl
)}
/> />
</Grid> </Grid>
) )
@@ -249,8 +262,8 @@ const Index = ({ data }: { data: Data }) => {
<Trans></Trans> <Trans></Trans>
</Typography> </Typography>
<Grid> <Grid>
{newsUrls.map(([title, date, url], _) => ( {newsUrls.map(([title, date, url], i) => (
<Grid container> <Grid container key={i}>
<Link href={url} underline="hover"> <Link href={url} underline="hover">
{title} {title}
</Link> </Link>

View File

@@ -8,11 +8,12 @@ import TableCell from '@mui/material/TableCell';
import TableHead from '@mui/material/TableHead'; import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow'; import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { Link } from 'gatsby-theme-material-ui';
import React, { memo } from 'react'; import React, { memo } from 'react';
import { MDXComponents } from '@mdx-js/react/lib';
import { LinkLink as Link } from '~/components/link-mui-components';
import CodeBlock from '../components/code-block'; import CodeBlock from '../components/code-block';
const components = { const components: MDXComponents = {
p: (() => { p: (() => {
const Paragraph = props => ( const Paragraph = props => (
<Typography {...props} style={{ margin: '8px 0' }} /> <Typography {...props} style={{ margin: '8px 0' }} />

View File

@@ -1,22 +1,20 @@
import { MDXProvider } from '@mdx-js/react'; import { MDXProvider } from '@mdx-js/react';
import { ArrowBack, ConnectedTvOutlined } from '@mui/icons-material';
import FolderIcon from '@mui/icons-material/Folder'; import FolderIcon from '@mui/icons-material/Folder';
import { Box, Grid, Typography } from '@mui/material'; import { Box, Grid, Typography } from '@mui/material';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
import { graphql } from 'gatsby'; import { graphql } from 'gatsby';
import { Trans, useI18next } from 'gatsby-plugin-react-i18next'; import { Trans, useI18next } from 'gatsby-plugin-react-i18next';
import { Button } from 'gatsby-theme-material-ui'; import { LinkButton as Button } from '~/components/link-mui-components';
import React, { useEffect, useState } from 'react'; import React, { PropsWithChildren, useEffect, useState } from 'react';
import Footer from '../components/footer'; import Footer from '../components/footer';
import FileList from '../components/file-list'; import FileList from '../components/file-list';
import LanguageIconButton from '../components/language-icon-button';
import Seo from '../components/seo'; import Seo from '../components/seo';
import StatusIndicator from '../components/status-indicator'; import StatusIndicator from '../components/status-indicator';
import ThemeIconButton from '../components/theme-icon-button'; import ThemeIconButton from '../components/theme-icon-button';
import { MirrorDto } from '../types/mirror'; import { Locale, MirrorDto } from '../types/mirror';
import { Link } from '../utils/i18n-link'; import { Link } from '../utils/i18n-link';
import components from './components'; import components from './components';
import { readCache, writeCache } from '../utils/cache'; import { popCache, writeCache } from '../utils/cache';
import { getUrl } from '../utils/url'; import { getUrl } from '../utils/url';
import TitleMirrorIcon from '../utils/title-mirror-icon'; import TitleMirrorIcon from '../utils/title-mirror-icon';
@@ -27,6 +25,10 @@ interface Data {
}; };
} }
type MirrorDocProps = {
data: Data;
};
async function fetchMirror(id: string): Promise<MirrorDto> { async function fetchMirror(id: string): Promise<MirrorDto> {
const res = await fetch(`/api/mirrors/${id}`); const res = await fetch(`/api/mirrors/${id}`);
if (!res.ok) { if (!res.ok) {
@@ -37,7 +39,7 @@ async function fetchMirror(id: string): Promise<MirrorDto> {
return json; return json;
} }
const MirrorDoc = ({ data, children }: { data: Data }) => { const MirrorDoc = ({ data, children }: PropsWithChildren<MirrorDocProps>) => {
const { language } = useI18next(); const { language } = useI18next();
const defaultData = { const defaultData = {
@@ -48,11 +50,12 @@ const MirrorDoc = ({ data, children }: { data: Data }) => {
}, },
status: 'unknown', status: 'unknown',
} as MirrorDto; } as MirrorDto;
const mirrorId = data.document.frontmatter.mirrorId; const { mirrorId } = data.document.frontmatter;
const [mirror, setMirror] = useState<MirrorDto>( const [mirror, setMirror] = useState<MirrorDto>({
readCache(`mirrors_${mirrorId}`, defaultData) ...defaultData,
); ...popCache(`mirrors_${mirrorId}`, defaultData),
});
useEffect(() => { useEffect(() => {
fetchMirror(mirrorId) fetchMirror(mirrorId)
.then(d => setMirror(d)) .then(d => setMirror(d))
@@ -62,7 +65,7 @@ const MirrorDoc = ({ data, children }: { data: Data }) => {
const fallbackUrl = `/${mirrorId}`; const fallbackUrl = `/${mirrorId}`;
const mirrorUrl = getUrl(mirror.url ?? fallbackUrl, false); const mirrorUrl = getUrl(mirror.url ?? fallbackUrl, false);
const name = mirror.name[language]; const name = mirror.name[language as Locale];
return ( return (
<Box <Box
sx={{ sx={{
@@ -90,11 +93,9 @@ const MirrorDoc = ({ data, children }: { data: Data }) => {
</Typography> </Typography>
</Link> </Link>
<Grid item> <Grid item>
{ {/* TODO: add English docs
/* TODO: add English docs
<LanguageIconButton /> <LanguageIconButton />
*/ */}
}
<ThemeIconButton /> <ThemeIconButton />
</Grid> </Grid>
</Grid> </Grid>

View File

@@ -1,23 +1,17 @@
import { MDXProvider } from '@mdx-js/react'; import { MDXProvider } from '@mdx-js/react';
import { ArrowBack, DateRange, AccountCircle } from '@mui/icons-material'; import { DateRange, AccountCircle } from '@mui/icons-material';
import FolderIcon from '@mui/icons-material/Folder';
import { Box, Grid, Typography } from '@mui/material'; import { Box, Grid, Typography } from '@mui/material';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
import { graphql } from 'gatsby'; import { graphql } from 'gatsby';
import { Trans, useI18next } from 'gatsby-plugin-react-i18next'; import { Trans, useI18next } from 'gatsby-plugin-react-i18next';
import { Button } from 'gatsby-theme-material-ui'; import React from 'react';
import React, { useEffect, useState } from 'react';
import Footer from '../components/footer'; import Footer from '../components/footer';
import FileList from '../components/file-list';
import LanguageIconButton from '../components/language-icon-button'; import LanguageIconButton from '../components/language-icon-button';
import Seo from '../components/seo'; import Seo from '../components/seo';
import StatusIndicator from '../components/status-indicator';
import ThemeIconButton from '../components/theme-icon-button'; import ThemeIconButton from '../components/theme-icon-button';
import { NewsDto } from '../types/news'; import { NewsDto } from '../types/news';
import { Link } from '../utils/i18n-link'; import { Link } from '../utils/i18n-link';
import components from './components'; import components from './components';
import { readCache, writeCache } from '../utils/cache';
import { getUrl } from '../utils/url';
interface Data { interface Data {
document: { document: {

View File

@@ -20,9 +20,9 @@ export interface MirrorDto {
name: Record<Locale, string>; name: Record<Locale, string>;
desc: Record<Locale, string>; desc: Record<Locale, string>;
status: MirrorStatus; status: MirrorStatus;
lastUpdated: string; lastUpdated: number;
nextScheduled: string; nextScheduled: number;
lastSuccess: string; lastSuccess: number;
url: string; url: string;
files?: File[]; files?: File[];
} }

View File

@@ -11,4 +11,10 @@ function readCache<T>(key: string, defaultValue: T): T {
return d ? JSON.parse(d) : defaultValue; return d ? JSON.parse(d) : defaultValue;
} }
export { writeCache, readCache }; function popCache<T>(key: string, defaultValue: T): T {
const d = readCache(key, defaultValue);
localStorage.removeItem(`${cachePrefix}${key}`);
return d;
}
export { writeCache, readCache, popCache };

View File

@@ -1,8 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { useI18next } from 'gatsby-plugin-react-i18next'; import { useI18next } from 'gatsby-plugin-react-i18next';
import { LANGUAGE_KEY } from 'gatsby-plugin-react-i18next/dist/types'; import { LANGUAGE_KEY } from 'gatsby-plugin-react-i18next/dist/types';
import { Link as MuiLink } from 'gatsby-theme-material-ui';
import { LinkProps } from '@mui/material'; import { LinkProps } from '@mui/material';
import { LinkLink as MuiLink } from '~/components/link-mui-components';
export function linkWithI18n< export function linkWithI18n<
P extends { to: string; onClick?: React.MouseEventHandler } P extends { to: string; onClick?: React.MouseEventHandler }

19
src/utils/root-layout.tsx Normal file
View File

@@ -0,0 +1,19 @@
import React from "react";
import { ThemeProvider } from "../components/theme-context";
import { CssBaseline } from "@mui/material";
import { PrefsProvider } from "../components/preferences-context";
export default function RootLayout({ children } : {
children: React.ReactNode;
}) {
return (
<React.StrictMode>
<PrefsProvider>
<ThemeProvider>
<CssBaseline />
{children}
</ThemeProvider>
</PrefsProvider>
</React.StrictMode>
);
}

View File

@@ -29,7 +29,10 @@
// "rootDir": "./", /* Specify the root folder within your source files. */ // "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
"~/*": ["./src/*"]
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
},
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */