This commit is contained in:
2023-11-02 14:47:08 +08:00
parent 8fa486e420
commit 03b0016db1
38 changed files with 12616 additions and 15652 deletions

View File

@@ -1 +0,0 @@
*.js

View File

@@ -1,36 +1,26 @@
// eslint-disable-next-line no-undef
module.exports = { module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
sourceType: "module",
project: "./tsconfig.json"
},
plugins: ["@typescript-eslint/eslint-plugin"],
extends: ["plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "plugin:react/recommended", "plugin:import/recommended", "plugin:import/typescript", "plugin:prettier/recommended", "plugin:storybook/recommended"],
root: true, root: true,
env: { env: { browser: true, es2020: true },
node: true, extends: [
jest: true "eslint:recommended",
}, "plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"plugin:prettier/recommended",
"plugin:storybook/recommended",
],
ignorePatterns: ["dist", "src/gql/*"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh", "prettier"],
rules: { rules: {
"prettier/prettier": ["error", { "react-refresh/only-export-components": [
singleQuote: false "warn",
}], { allowConstantExport: true },
"@typescript-eslint/explicit-function-return-type": "off", ],
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-unused-vars": [
"@typescript-eslint/quotes": [2, "double", "avoid-escape"], "error",
"no-unused-vars": "off", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_" }], ],
"quotes": [2, "double", "avoid-escape"], },
"semi": ["error", "always"], };
"eol-last": ["error", "always"],
"react/react-in-jsx-scope": "off",
"import/no-unresolved": "off",
"import/order": ["error", {
"newlines-between": "always",
"alphabetize": {
"order": "asc"
}
}]
}
};

2
.gitignore vendored
View File

@@ -22,3 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.vscode

View File

@@ -5,23 +5,31 @@
# Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
# Note that environment variables can be set in several places # Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence
image: node:lts
before_script:
- yarn install --frozen-lockfile
stages: stages:
- release - release
- test - test
- deploy - deploy
pages: pages:
image: node:18.17.1
before_script:
- corepack enable
- corepack prepare pnpm@latest-8 --activate
- pnpm config set store-dir .pnpm-store
script: script:
- yarn build - pnpm install # install dependencies
- mv dist public cache:
key:
files:
- pnpm-lock.yaml
paths:
- .pnpm-store
artifacts: artifacts:
paths: paths:
- public - public
rules: rules:
- if: "$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH" - if: "$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH"
include: include:
- project: template/gitlabci-template - template: Security/Dependency-Scanning.gitlab-ci.yml
file: docker.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml

View File

@@ -0,0 +1,18 @@
import { DocsContainer as BaseContainer } from "@storybook/blocks";
import { themes } from "@storybook/theming";
import React from "react";
import { useDarkMode } from "storybook-dark-mode";
export const DocsContainer = ({ children, context }) => {
const dark = useDarkMode();
return (
<BaseContainer
context={context}
theme={dark ? themes.dark : themes.light}
>
{children}
</BaseContainer>
);
};

View File

@@ -1,20 +1,20 @@
module.exports = { import type { StorybookConfig } from "@storybook/react-vite";
"stories": [
"../src/**/*.stories.mdx", const config: StorybookConfig = {
"../src/**/*.stories.@(js|jsx|ts|tsx)" stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
], addons: [
"addons": [
"@storybook/addon-links", "@storybook/addon-links",
"storybook-dark-mode",
"@storybook/addon-essentials", "@storybook/addon-essentials",
"@storybook/addon-interactions", "@storybook/addon-interactions",
"storybook-addon-react-router-v6", "storybook-dark-mode",
"storybook-react-i18next"
], ],
"framework": "@storybook/react", framework: {
"core": { name: "@storybook/react-vite",
"builder": "@storybook/builder-vite" options: {},
}, },
"features": { docs: {
"storyStoreV7": true autodocs: "tag",
} },
} };
export default config;

View File

@@ -1,3 +1,3 @@
<script> <link rel="preconnect" href="https://fonts.googleapis.com">
window.global = window; <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
</script> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@100;400&display=swap" rel="stylesheet">

View File

@@ -1,61 +1,50 @@
import { import { Container, MantineProvider } from "@mantine/core";
ActionIcon, Affix, ColorSchemeProvider, createEmotionCache, MantineProvider import '@mantine/core/styles.css';
} from '@mantine/core'; import type { Preview } from "@storybook/react";
import { useHotkeys } from '@mantine/hooks';
import React, { useState } from 'react';
import { useDarkMode } from 'storybook-dark-mode'; import { useDarkMode } from 'storybook-dark-mode';
import rtlPlugin from 'stylis-plugin-rtl';
export const parameters = {
layout: 'fullscreen' , import i18n from "../src/i18n";
actions: { argTypesRegex: "^on[A-Z].*" },
controls: { import React, { Fragment } from "react";
matchers: { import { DocsContainer } from "./DocsContainer";
color: /(background|color)$/i,
date: /Date$/, const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
docs: {
container: DocsContainer,
},
i18n
},
globals: {
locale: 'en',
locales: {
en: 'English',
zh: '中文',
}, },
}, },
}; decorators: [
const rtlCache = createEmotionCache({ key: 'mantine-rtl', stylisPlugins: [rtlPlugin] }); (Story, runtime) => {
const isDark = useDarkMode();
function ThemeWrapper(props: any) { const C = runtime.parameters.layout === 'fullscreen' ? Fragment : Container;
const [rtl, setRtl] = useState(false);
const toggleRtl = () => setRtl((r) => !r); return <MantineProvider withCssVariables forceColorScheme={
useHotkeys([['mod + L', toggleRtl]]); isDark ? 'dark' : 'light'
}>
return ( <C>
<ColorSchemeProvider colorScheme="light" toggleColorScheme={() => {}}> <Story />
<MantineProvider </C>
theme={{
dir: rtl ? 'rtl' : 'ltr',
colorScheme: useDarkMode() ? 'dark' : 'light',
headings: { fontFamily: 'Greycliff CF, sans-serif' },
}}
emotionCache={rtl ? rtlCache : undefined}
withGlobalStyles
withNormalizeCSS
>
<Affix position={{ right: rtl ? 'unset' : 0, left: rtl ? 0 : 'unset', bottom: 0 }}>
<ActionIcon
onClick={toggleRtl}
variant="default"
style={{
borderBottom: 0,
borderRight: 0,
borderTopLeftRadius: 4,
width: 60,
fontWeight: 700,
}}
radius={0}
size={30}
>
{rtl ? 'RTL' : 'LTR'}
</ActionIcon>
</Affix>
<div dir={rtl ? 'rtl' : 'ltr'}>{props.children}</div>
</MantineProvider> </MantineProvider>
</ColorSchemeProvider> },
); ]
} };
export const decorators = [(renderStory: any) => <ThemeWrapper>{renderStory()}</ThemeWrapper>]; export default preview;

21
.vscode/settings.json vendored
View File

@@ -1,21 +0,0 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.codeActionsOnSave": [
"source.organizeImports",
"source.fixAll.eslint"
],
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"cSpell.words": [
"mantine",
"MINIO",
"zincsearch"
],
"i18n-ally.localesPaths": [
"src/locales"
]
}

View File

@@ -1,25 +0,0 @@
FROM node:18-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn config set registry https://nexus.yoshino-s.xyz/repository/npm/
RUN yarn --frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build
FROM nginx:1.21-alpine AS runner
COPY default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /static
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/assets//favicon.png" /> <link rel="icon" type="image/svg+xml" href="/src/assets//favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Mantine App</title> <title>DS-Next</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -16,75 +16,79 @@
"chromatic": "npx chromatic --project-token=180ac2186305" "chromatic": "npx chromatic --project-token=180ac2186305"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.5", "@mantine/core": "^7.1.7",
"@mantine/core": "6.0.0", "@mantine/ds": "^7.1.7",
"@mantine/form": "^6.0.0", "@mantine/hooks": "^7.1.7",
"@mantine/hooks": "^6.0.0", "@mantine/notifications": "^7.1.7",
"@mantine/notifications": "^6.0.0", "@reduxjs/toolkit": "^1.9.7",
"@reduxjs/toolkit": "^1.9.3", "@tabler/icons-react": "^2.40.0",
"@tabler/icons-react": "^2.30.0", "axios": "^1.6.0",
"axios": "^1.3.4", "dayjs": "^1.11.10",
"dayjs": "^1.11.7", "i18next": "^23.6.0",
"i18next": ">=21.0.0", "i18next-browser-languagedetector": "^7.1.0",
"i18next-browser-languagedetector": "^6.1.4",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^18.0.0", "react": "^18.2.0",
"react-dom": "^18.0.0", "react-dom": "^18.2.0",
"react-i18next": "^11.17.1", "react-i18next": "^13.3.1",
"react-redux": "^8.0.5", "react-redux": "^8.1.3",
"react-router-dom": "^6.3.0", "react-router-dom": "^6.18.0",
"remark": "^14.0.2", "remark": "^15.0.1",
"remark-html": "^15.0.2" "remark-html": "^16.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.0", "@babel/core": "^7.23.2",
"@storybook/addon-actions": "^6.5.16", "@mantine/form": "^7.1.7",
"@storybook/addon-essentials": "^6.5.16", "@storybook/addon-actions": "^7.5.2",
"@storybook/addon-interactions": "^6.5.16", "@storybook/addon-essentials": "^7.5.2",
"@storybook/addon-links": "^6.5.16", "@storybook/addon-interactions": "^7.5.2",
"@storybook/addons": ">=6.5.0", "@storybook/addon-links": "^7.5.2",
"@storybook/api": ">=6.5.0", "@storybook/addons": "^7.5.2",
"@storybook/builder-vite": "^0.4.2", "@storybook/api": "^7.5.2",
"@storybook/components": ">=6.5.0", "@storybook/builder-vite": "^7.5.2",
"@storybook/core-events": ">=6.5.0", "@storybook/components": "^7.5.2",
"@storybook/react": "^6.5.16", "@storybook/core-events": "^7.5.2",
"@storybook/testing-library": "^0.0.13", "@storybook/react": "^7.5.2",
"@storybook/theming": ">=6.5.0", "@storybook/testing-library": "^0.2.2",
"@testing-library/dom": "^8.20.0", "@storybook/theming": "^7.5.2",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/dom": "^9.3.3",
"@testing-library/react": "^13.4.0", "@testing-library/jest-dom": "^6.1.4",
"@testing-library/user-event": "^14.4.3", "@testing-library/react": "^14.0.0",
"@types/jest": "^29.4.0", "@testing-library/user-event": "^14.5.1",
"@types/lodash": "^4.14.191", "@types/jest": "^29.5.7",
"@types/react": "^18.0.27", "@types/lodash": "^4.14.200",
"@types/react-dom": "^18.0.10", "@types/react": "^18.2.33",
"@typescript-eslint/eslint-plugin": "^5.50.0", "@types/react-dom": "^18.2.14",
"@typescript-eslint/parser": "^5.50.0", "@typescript-eslint/eslint-plugin": "^6.9.1",
"@vitejs/plugin-react": "^3.1.0", "@typescript-eslint/parser": "^6.9.1",
"babel-loader": "^8.3.0", "@vitejs/plugin-react": "^4.1.0",
"chromatic": "^6.17.1", "babel-loader": "^9.1.3",
"eslint": "^8.33.0", "chromatic": "^7.6.0",
"eslint-config-prettier": "^8.6.0", "eslint": "^8.52.0",
"eslint-import-resolver-typescript": "^3.5.3", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.27.5", "eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-storybook": "^0.6.11", "eslint-plugin-react-refresh": "^0.4.4",
"i18next-http-backend": "^1.4.0", "eslint-plugin-storybook": "^0.6.15",
"i18next-http-backend": "^2.3.1",
"install-peerdeps": "^3.0.3", "install-peerdeps": "^3.0.3",
"jest": "^29.4.1", "jest": "^29.7.0",
"jest-environment-jsdom": "^29.4.1", "jest-environment-jsdom": "^29.7.0",
"prettier": "^2.8.3", "postcss": "^8.4.31",
"react-router": "^6.3.0", "postcss-preset-mantine": "^1.9.0",
"storybook": "^6.5.16", "postcss-simple-vars": "^7.0.1",
"storybook-addon-react-router-v6": "0.2.1", "prettier": "^3.0.3",
"storybook-dark-mode": "^2.1.1", "react-router": "^6.18.0",
"storybook-react-i18next": "1.1.2", "storybook": "^7.5.2",
"storybook-addon-react-router-v6": "2.0.8",
"storybook-dark-mode": "^3.0.1",
"storybook-react-i18next": "2.0.9",
"stylis-plugin-rtl": "^2.1.1", "stylis-plugin-rtl": "^2.1.1",
"ts-jest": "^29.0.5", "ts-jest": "^29.1.1",
"typescript": "^4.9.5", "typescript": "^5.2.2",
"vite": "^4.1.1" "vite": "^4.5.0"
} }
} }

12076
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

15
postcss.config.js Normal file
View File

@@ -0,0 +1,15 @@
// eslint-disable-next-line no-undef
module.exports = {
plugins: {
"postcss-preset-mantine": {},
"postcss-simple-vars": {
variables: {
"mantine-breakpoint-xs": "36em",
"mantine-breakpoint-sm": "48em",
"mantine-breakpoint-md": "62em",
"mantine-breakpoint-lg": "75em",
"mantine-breakpoint-xl": "88em",
},
},
},
};

View File

@@ -1,34 +0,0 @@
import { ColorSchemeProvider, MantineProvider } from "@mantine/core";
import { useEffect } from "react";
import { usePreferenceState } from "./store/module/preference";
interface ThemeProviderProps {
children: React.ReactNode;
}
export function ThemeProvider({ children }: ThemeProviderProps) {
const {
state: { colorScheme },
toggleColorScheme,
} = usePreferenceState();
useEffect(() => {
console.log(colorScheme);
}, []);
return (
<ColorSchemeProvider
colorScheme={colorScheme}
toggleColorScheme={toggleColorScheme}
>
<MantineProvider
theme={{ colorScheme }}
withGlobalStyles
withNormalizeCSS
>
{children}
</MantineProvider>
</ColorSchemeProvider>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -1,31 +0,0 @@
import { ActionIcon, Group, useMantineColorScheme } from "@mantine/core";
import { IconMoonStars, IconSun } from "@tabler/icons-react";
export function ColorSchemeToggle() {
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
return (
<Group position="center" mt="xl">
<ActionIcon
onClick={() => toggleColorScheme()}
size="xl"
sx={(theme) => ({
backgroundColor:
theme.colorScheme === "dark"
? theme.colors.dark[6]
: theme.colors.gray[0],
color:
theme.colorScheme === "dark"
? theme.colors.yellow[4]
: theme.colors.blue[6],
})}
>
{colorScheme === "dark" ? (
<IconSun size={20} stroke={1.5} />
) : (
<IconMoonStars size={20} stroke={1.5} />
)}
</ActionIcon>
</Group>
);
}

View File

@@ -1,105 +1,5 @@
import { import { Dispatch, SetStateAction, createContext } from "react";
ActionIcon,
Group,
Header,
Text,
TextInput,
createStyles,
rem,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { useDocumentTitle, useMediaQuery } from "@mantine/hooks";
import { IconSearch, IconSettings } from "@tabler/icons-react";
import { Dispatch, SetStateAction, createContext, useContext } from "react";
import { useNavigate } from "react-router";
import { Link } from "react-router-dom";
const useStyles = createStyles((theme) => ({
header: {
paddingLeft: theme.spacing.md,
paddingRight: theme.spacing.md,
},
inner: {
height: rem(56),
display: "flex",
justifyContent: "space-between",
alignItems: "center",
},
link: {
display: "block",
lineHeight: 1,
padding: `${rem(8)} ${rem(12)}`,
borderRadius: theme.radius.sm,
textDecoration: "none",
color:
theme.colorScheme === "dark"
? theme.colors.dark[0]
: theme.colors.gray[7],
fontSize: theme.fontSizes.sm,
fontWeight: 500,
"&:hover": {
backgroundColor:
theme.colorScheme === "dark"
? theme.colors.dark[6]
: theme.colors.gray[0],
},
},
}));
export const TitleContext = createContext< export const TitleContext = createContext<
[string, Dispatch<SetStateAction<string>>] [string, Dispatch<SetStateAction<string>>]
>(["DS-Next", () => 0]); >(["DS-Next", () => 0]);
export function HeaderSearch() {
const { classes } = useStyles();
const [title, _] = useContext(TitleContext);
const isMobile = useMediaQuery("(max-width: 768px)");
useDocumentTitle(title);
const navigate = useNavigate();
const form = useForm({
initialValues: {
search: "",
},
});
function submit({ search }: { search: string }) {
console.log(search);
navigate(`/search/${encodeURIComponent(search)}`);
}
return (
<Header height={56} className={classes.header}>
<div className={classes.inner}>
<span>
<Text weight={600} component={Link} to="/">
DS-Next
</Text>
{!isMobile && (
<Text weight={600} component="span">
{" | "}
{title}
</Text>
)}
</span>
<Group>
<form onSubmit={form.onSubmit(submit)}>
<TextInput
placeholder="Search"
icon={<IconSearch size="1rem" stroke={1.5} />}
{...form.getInputProps("search")}
required
/>
</form>
<ActionIcon component={Link} to="/settings">
<IconSettings />
</ActionIcon>
</Group>
</div>
</Header>
);
}

View File

@@ -1,27 +1,10 @@
import { Card, Group, Image, Text, createStyles } from "@mantine/core"; import { Badge, Card, Group, Image, Text } from "@mantine/core";
import dayjs from "dayjs"; import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
const useStyles = createStyles((theme) => ({
card: {
backgroundColor:
theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.white,
},
title: {
fontWeight: 700,
fontFamily: `Greycliff CF, ${theme.fontFamily}`,
lineHeight: 1.2,
},
body: {
padding: theme.spacing.md,
},
}));
export function ParagraphCard({ export function ParagraphCard({
cover, cover,
title, title,
@@ -30,56 +13,49 @@ export function ParagraphCard({
tags, tags,
_id, _id,
}: Paragraph) { }: Paragraph) {
const { classes } = useStyles();
const url = `/paragraph/${_id}`; const url = `/paragraph/${_id}`;
return ( return (
<Card withBorder radius="md" p={0} className={classes.card}> <Card withBorder radius="md" padding="lg" shadow="sm">
<Group noWrap spacing={0}> <Card.Section>
<Link to={url}> <Link to={url}>
{cover && <Image src={cover} height={140} width={140} />} {cover && <Image src={cover} height={140} width={140} />}
</Link> </Link>
<div className={classes.body}> </Card.Section>
<Text transform="uppercase" color="dimmed" weight={700} size="xs"> <Group justify="space-between" mt="md" mb="xs">
{tags.map((tag, index) => ( <Text component={Link} to={url}>
<> {title}
{index > 0 && " • "} </Text>
<Text <Group>
component={Link} {tags.map((tag, index) => (
key={index} <>
to={`/tag/${encodeURIComponent(tag)}`} <Badge
>
{tag}
</Text>
</>
))}
</Text>
<Text
className={classes.title}
mt="xs"
mb="md"
component={Link}
to={url}
>
{title}
</Text>
<Group noWrap spacing="xs">
<Group spacing="xs" noWrap>
<Text
size="xs"
component={Link} component={Link}
to={`/author/${encodeURIComponent(author)}`} key={index}
to={`/tag/${encodeURIComponent(tag)}`}
> >
{author} {tag}
</Text> </Badge>
</Group> </>
<Text size="xs" color="dimmed"> ))}
</Group>
</Text> </Group>
<Text size="xs" color="dimmed">
{dayjs().to(dayjs(time))} <Group>
</Text> <Group>
</Group> <Text
</div> size="xs"
component={Link}
to={`/author/${encodeURIComponent(author)}`}
>
{author}
</Text>
</Group>
<Text size="xs" c="dimmed">
</Text>
<Text size="xs" c="dimmed">
{dayjs().to(dayjs(time))}
</Text>
</Group> </Group>
</Card> </Card>
); );

View File

@@ -12,7 +12,7 @@ export function ThemeSetting() {
return ( return (
<SegmentedControl <SegmentedControl
value={colorScheme} value={colorScheme}
onChange={(value: "light" | "dark") => toggleColorScheme(value)} onChange={toggleColorScheme}
data={[ data={[
{ {
value: "light", value: "light",

View File

@@ -7,11 +7,11 @@ export interface PaginationParams {
} }
const api = axios.create({ const api = axios.create({
withCredentials: false,
auth: { auth: {
username: "viewer", username: "viewer",
password: "publicviewer1", password: "publicviewer1",
}, },
baseURL: "https://api.ourdomain.com",
}); });
api.interceptors.response.use( api.interceptors.response.use(
@@ -36,17 +36,17 @@ api.interceptors.response.use(
autoClose: true, autoClose: true,
}); });
} }
} },
); );
export class SearchApi { export class SearchApi {
static async search( static async search(
baseUrl: string, baseUrl: string,
query: ZincQueryForSDK query: ZincQueryForSDK,
): Promise<SearchResponse> { ): Promise<SearchResponse> {
const { data } = await api.post( const { data } = await api.post(
new URL("/api/paragraph/_search", baseUrl).toString(), new URL("/api/paragraph/_search", baseUrl).toString(),
query query,
); );
return data; return data;
} }
@@ -61,7 +61,7 @@ export class SearchApi {
} }
static async getParagraph(baseUrl: string, id: string) { static async getParagraph(baseUrl: string, id: string) {
const { data } = await api.get( const { data } = await api.get(
new URL(`/api/paragraph/_doc/${id}`, baseUrl).toString() new URL(`/api/paragraph/_doc/${id}`, baseUrl).toString(),
); );
return data._source; return data._source;
} }

View File

@@ -1,11 +1,11 @@
import { Pagination } from "@mantine/core"; import { Pagination } from "@mantine/core";
import { merge } from "lodash";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import { SearchApi } from "./api";
import { useOptionsState } from "@/store/module/options"; import { useOptionsState } from "@/store/module/options";
import { useDebounceCallback } from "@mantine/hooks";
import { merge } from "lodash";
import { SearchApi } from "./api";
export function usePaginationData<T>(query: ZincQueryForSDK) { export function usePaginationData<T>(query: ZincQueryForSDK) {
const [params, setParams] = useSearchParams({ const [params, setParams] = useSearchParams({
@@ -15,51 +15,49 @@ export function usePaginationData<T>(query: ZincQueryForSDK) {
const { state: options } = useOptionsState(); const { state: options } = useOptionsState();
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [data, setData] = useState<T[]>([]); const [data, setData] = useState<T[]>([]);
const [skip, setSkip] = useState(0);
const [take, _] = useState(parseInt(params.get("size") || "10")); const [take, _] = useState(parseInt(params.get("size") || "10"));
const [page, setPage] = useState(parseInt(params.get("page") || "1")); const [page, setPage] = useState(parseInt(params.get("page") || "1"));
function refresh() { const update = useDebounceCallback(async function update() {
SearchApi.search( console.log("query", query, page, take, options);
const resp = await SearchApi.search(
options.zincsearchUrl, options.zincsearchUrl,
merge({}, query, { merge(
from: skip, {
max_results: take, search_type: "matchall",
}) sort_fields: ["-@timestamp"],
).then((resp) => { _source: ["title", "cover", "author", "tags"],
setTotal(resp.hits.total.value); },
setData( query,
resp.hits.hits.map((hit) => {
SearchApi.wrapParagraph( from: (page - 1) * take,
options.s3Url, max_results: take,
merge({ _id: hit._id }, hit._source) },
) ),
) as T[] );
); setTotal(resp.hits.total.value);
}); setData(
} resp.hits.hits.map((hit) =>
SearchApi.wrapParagraph(
options.s3Url,
merge({ _id: hit._id, "@timestamp": hit["@timestamp"] }, hit._source),
),
) as T[],
);
}, 200);
useEffect(update, [query, page, take, options, update]);
useEffect(() => { useEffect(() => {
refresh();
}, [skip, take]);
useEffect(() => {
console.log("set params");
setParams({ setParams({
size: take.toString(), size: take.toString(),
page: page.toString(), page: page.toString(),
}); });
}, [take, page]); }, [take, page, setParams]);
useEffect(() => {
setSkip((page - 1) * take);
}, [page]);
return { return {
total,
data, data,
page, page,
refresh,
pagination: ( pagination: (
<Pagination <Pagination
total={Math.ceil(total / take)} total={Math.ceil(total / take)}

View File

@@ -1,41 +1,103 @@
import { Box, Container, Flex, ScrollArea, createStyles } from "@mantine/core"; import {
import { Suspense, useState } from "react"; Affix,
import { Outlet } from "react-router"; AppShell,
Avatar,
Button,
Group,
Text,
TextInput,
Transition,
UnstyledButton,
rem,
} from "@mantine/core";
import { Suspense, useCallback, useState } from "react";
import { Outlet, useNavigate } from "react-router";
import { HeaderSearch, TitleContext } from "@/component/Header/Header"; import { TitleContext } from "@/component/Header/Header";
import Loading from "@/page/Loading"; import Loading from "@/page/Loading";
const useStyles = createStyles((theme) => ({ import { useForm } from "@mantine/form";
contentContainer: { import { useHeadroom, useWindowScroll } from "@mantine/hooks";
backgroundColor: import { IconArrowUp, IconSearch, IconSettings } from "@tabler/icons-react";
theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.white, import { Link } from "react-router-dom";
width: "100vw",
overflow: "hidden",
},
rootContainer: {
height: "100vh",
width: "100vw",
},
}));
export default function MainLayout() { export default function MainLayout() {
const { classes } = useStyles();
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const pinned = useHeadroom({ fixedAt: 60 });
const [scroll, scrollTo] = useWindowScroll();
const navigate = useNavigate();
const form = useForm({
initialValues: {
search: "",
},
});
const search = useCallback(function submit({ search }: { search: string }) {
console.log(search);
navigate(`/search/${encodeURIComponent(search)}`);
}, []);
return ( return (
<TitleContext.Provider value={[title, setTitle]}> <TitleContext.Provider value={[title, setTitle]}>
<Flex direction="column" className={classes.rootContainer}> <AppShell
<HeaderSearch /> header={{ height: 60, collapsed: !pinned, offset: false }}
<Box className={classes.contentContainer}> padding="md"
h="100vh"
>
<AppShell.Header>
<Group h="100%" justify="space-between" px="md">
<Group>
<Avatar fw={700} component={Link} to="/">
DS
</Avatar>
<Text size="lg" fw={700} ml="sm">
{title}
</Text>
</Group>
<Group>
<form onSubmit={form.onSubmit(search)}>
<TextInput
placeholder="Search"
{...form.getInputProps("search")}
leftSection={
<IconSearch
style={{ width: rem(16), height: rem(16) }}
stroke={1.5}
/>
}
/>
</form>
<UnstyledButton component={Link} to="/settings">
<IconSettings />
</UnstyledButton>
</Group>
</Group>
</AppShell.Header>
<AppShell.Main pt={`calc(${rem(60)} + var(--mantine-spacing-md))`}>
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
<ScrollArea h="100%" w="100vw"> <Outlet />
<Container maw="100vw">
<Outlet />
</Container>
</ScrollArea>
</Suspense> </Suspense>
</Box> </AppShell.Main>
</Flex> </AppShell>
<Affix position={{ bottom: 20, right: 20 }}>
<Transition transition="slide-up" mounted={scroll.y > 0}>
{(transitionStyles) => (
<Button
leftSection={
<IconArrowUp style={{ width: rem(16), height: rem(16) }} />
}
style={transitionStyles}
onClick={() => scrollTo({ y: 0 })}
>
Scroll to top
</Button>
)}
</Transition>
</Affix>
</TitleContext.Provider> </TitleContext.Provider>
); );
} }

View File

@@ -2,16 +2,21 @@ import { Notifications } from "@mantine/notifications";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import { MantineProvider, createTheme } from "@mantine/core";
import App from "./App"; import App from "./App";
import { ThemeProvider } from "./ThemeProvider";
import store from "./store"; import store from "./store";
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion import "@mantine/core/styles.css";
const theme = createTheme({
/** Put your mantine theme override here */
});
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<Provider store={store}> <Provider store={store}>
<ThemeProvider> <MantineProvider withCssVariables theme={theme}>
<Notifications /> <Notifications />
<App /> <App />
</ThemeProvider> </MantineProvider>
</Provider> </Provider>,
); );

View File

@@ -1,106 +0,0 @@
import {
Button,
Center,
Container,
createStyles,
Group,
rem,
Text,
Title,
} from "@mantine/core";
import { useDocumentTitle } from "@mantine/hooks";
import { useNavigate, useRouteError } from "react-router-dom";
const useStyles = createStyles((theme) => ({
root: {
backgroundColor: theme.fn.variant({
variant: "filled",
color: theme.primaryColor,
}).background,
minHeight: "100vh",
},
label: {
textAlign: "center",
fontWeight: 900,
fontSize: rem(220),
lineHeight: 1,
marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
color: theme.colors[theme.primaryColor][3],
[theme.fn.smallerThan("sm")]: {
fontSize: rem(120),
},
},
title: {
fontFamily: `Greycliff CF, ${theme.fontFamily}`,
textAlign: "center",
fontWeight: 900,
fontSize: rem(38),
color: theme.white,
[theme.fn.smallerThan("sm")]: {
fontSize: rem(32),
},
},
description: {
maxWidth: rem(540),
margin: "auto",
marginTop: theme.spacing.xl,
marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
color: theme.colors[theme.primaryColor][1],
},
}));
export interface ErrorPageProps {
label?: string | null;
title?: string | null;
description?: string | null;
}
export default function ErrorPage(props: ErrorPageProps) {
const navigate = useNavigate();
const { classes } = useStyles();
const error = useRouteError();
useDocumentTitle(props.label ?? "Error");
return (
<Center className={classes.root}>
<Container>
<div className={classes.label}>{props.label ?? "Error"}</div>
<Title className={classes.title}>
{props.title ?? "An error occurred"}
</Title>
<Text size="lg" align="center" className={classes.description}>
{props.description ??
error?.toString?.() ??
"An error occurred while loading the page."}
</Text>
<Group position="center">
<Button variant="white" size="md" onClick={() => navigate(0)}>
Refresh
</Button>{" "}
or
<Button variant="white" size="md" onClick={() => navigate(-1)}>
Go Back
</Button>
</Group>
</Container>
</Center>
);
}
export function NotFoundPage() {
return (
<ErrorPage
label="404"
title="Page not found"
description="The page you are looking for might have been removed, had its name changed, or is temporarily unavailable."
/>
);
}

View File

@@ -0,0 +1,40 @@
.root {
padding-top: rem(80px);
padding-bottom: rem(120px);
background-color: var(--mantine-color-blue-filled);
}
.label {
text-align: center;
font-weight: 900;
font-size: rem(220px);
line-height: 1;
margin-bottom: calc(var(--mantine-spacing-xl) * 1.5);
color: var(--mantine-color-blue-3);
@media (max-width: $mantine-breakpoint-sm) {
font-size: rem(120px);
}
}
.title {
font-family:
Greycliff CF,
var(--mantine-font-family);
text-align: center;
font-weight: 900;
font-size: rem(38px);
color: var(--mantine-color-white);
@media (max-width: $mantine-breakpoint-sm) {
font-size: rem(32px);
}
}
.description {
max-width: rem(540px);
margin: auto;
margin-top: var(--mantine-spacing-xl);
margin-bottom: calc(var(--mantine-spacing-xl) * 1.5);
color: var(--mantine-color-blue-1);
}

View File

@@ -0,0 +1,27 @@
import { Button, Container, Group, Text, Title } from "@mantine/core";
import classes from "./ErrorPage.module.css";
export interface ErrorPageProps {
label?: string | null;
title?: string | null;
description?: string | null;
}
export default function ErrorPage(props: ErrorPageProps) {
return (
<div className={classes.root}>
<Container>
<div className={classes.label}>{props.label}</div>
<Title className={classes.title}>{props.title}</Title>
<Text size="lg" ta="center" className={classes.description}>
{props.description}
</Text>
<Group justify="center">
<Button variant="white" size="md">
Refresh the page
</Button>
</Group>
</Container>
</div>
);
}

View File

@@ -0,0 +1,11 @@
import ErrorPage from "./ErrorPage";
export default function NotFoundPage() {
return (
<ErrorPage
label="404"
title="Page not found"
description="The page you are looking for might have been removed, had its name changed, or is temporarily unavailable."
/>
);
}

View File

@@ -1,22 +1,12 @@
import { createStyles, LoadingOverlay } from "@mantine/core"; import { LoadingOverlay } from "@mantine/core";
import { useDocumentTitle } from "@mantine/hooks";
const useStyles = createStyles((theme) => ({
root: {
height: "100vh",
backgroundColor: theme.fn.variant({
variant: "filled",
color: theme.primaryColor,
}).background,
fontSize: theme.fontSizes.xl,
},
}));
export default function Loading() { export default function Loading() {
const { classes } = useStyles();
useDocumentTitle("Loading");
return ( return (
<div className={classes.root}> <div
style={{
height: "100vh",
}}
>
<LoadingOverlay visible /> <LoadingOverlay visible />
</div> </div>
); );

View File

@@ -5,15 +5,11 @@ import {
Text, Text,
Title, Title,
TypographyStylesProvider, TypographyStylesProvider,
UnstyledButton,
createStyles,
} from "@mantine/core"; } from "@mantine/core";
import dayjs from "dayjs"; import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
import { useContext, useEffect } from "react";
import { useLoaderData } from "react-router"; import { useLoaderData } from "react-router";
import { TitleContext } from "@/component/Header/Header";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
function stripStyles(content: string) { function stripStyles(content: string) {
@@ -54,58 +50,50 @@ function stripStyles(content: string) {
return element.innerHTML; return element.innerHTML;
} }
const useStyles = createStyles(() => ({
paragraph: {
lineBreak: "anywhere",
},
}));
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
export default function ParagraphPage() { export default function ParagraphPage() {
const { classes } = useStyles();
const [_, setTitle] = useContext(TitleContext);
const paragraph = useLoaderData() as Paragraph; const paragraph = useLoaderData() as Paragraph;
useEffect(() => {
setTitle(paragraph.title);
}, [paragraph]);
return ( return (
<Container py="2rem"> <Container py="2rem">
<Title mb="xl">{paragraph.title}</Title> <Title mb="xl">{paragraph.title}</Title>
<Group position="apart"> <Group justify="space-between" align="center">
<Group> <Group>
<Text c="dimmed"> {dayjs().to(dayjs(paragraph.time))}</Text> <Text c="dimmed"> {dayjs().to(dayjs(paragraph.time))}</Text>
<UnstyledButton <Badge
ml="1rem"
radius="sm"
component={Link} component={Link}
to={`/author/${encodeURIComponent( to={`/author/${encodeURIComponent(paragraph.author || "unknown")}`}
paragraph.author || "unknown"
)}`}
> >
<Badge ml="1rem" radius="sm"> {paragraph.author}
{paragraph.author} </Badge>
</Badge> </Group>
</UnstyledButton> <Group>
{paragraph.tags.map((tag, index) => (
<>
<Badge
component={Link}
key={index}
to={`/tag/${encodeURIComponent(tag)}`}
>
{tag}
</Badge>
</>
))}
</Group> </Group>
</Group> </Group>
<Group mb="xl">
{paragraph.tags?.map((tag) => ( <TypographyStylesProvider
<UnstyledButton style={{
key={tag} paddingLeft: 0,
component={Link} }}
to={`/tag/${encodeURIComponent(tag)}`} >
>
<Badge fz="xs" variant="dot">
{tag}
</Badge>
</UnstyledButton>
))}
</Group>
<TypographyStylesProvider>
<div <div
className={classes.paragraph} style={{
lineBreak: "anywhere",
}}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: stripStyles(paragraph.content), __html: stripStyles(paragraph.content),
}} }}

View File

@@ -1,5 +1,4 @@
import { Grid, Group } from "@mantine/core"; import { Grid, Group } from "@mantine/core";
import { merge } from "lodash";
import { useContext, useEffect } from "react"; import { useContext, useEffect } from "react";
import { useLoaderData, useLocation, useParams } from "react-router"; import { useLoaderData, useLocation, useParams } from "react-router";
@@ -8,36 +7,16 @@ import { ParagraphCard } from "../component/ParagraphCard/ParagraphCard";
import { TitleContext } from "@/component/Header/Header"; import { TitleContext } from "@/component/Header/Header";
import { usePaginationData } from "@/helper/hooks"; import { usePaginationData } from "@/helper/hooks";
export interface SearchPageProps { export default function SearchPage() {
query?: ZincQueryForSDK; const [_title, setTitle] = useContext(TitleContext);
}
export default function SearchPage(props: SearchPageProps) { const params = useLoaderData() as ZincQueryForSDK;
const [_, setTitle] = useContext(TitleContext);
const params = useLoaderData();
const query: ZincQueryForSDK = merge(
{
search_type: "matchall",
sort_fields: ["-@timestamp"],
_source: ["title", "cover", "author", "tags"],
},
props.query,
params
);
const { const {
page, page,
pagination, pagination,
refresh,
data: paragraphs, data: paragraphs,
} = usePaginationData<Paragraph>(query); } = usePaginationData<Paragraph>(params);
useEffect(() => {
console.log("refresh");
refresh();
}, [params]);
const location = useLocation(); const location = useLocation();
const param = useParams(); const param = useParams();
@@ -53,20 +32,20 @@ export default function SearchPage(props: SearchPageProps) {
} }
const title = `${action} Page 1`; const title = `${action} Page 1`;
setTitle(title); setTitle(title);
}, [page]); }, [page, location, param, setTitle]);
return ( return (
<div> <div>
<Grid my="md"> <Grid my="md">
{paragraphs.map((paragraph) => { {paragraphs.map((paragraph) => {
return ( return (
<Grid.Col xs={12} sm={6} key={paragraph._id}> <Grid.Col span={{ base: 12, sm: 6 }} key={paragraph._id}>
<ParagraphCard {...paragraph} key={paragraph._id} /> <ParagraphCard {...paragraph} key={`${paragraph._id}_card`} />
</Grid.Col> </Grid.Col>
); );
})} })}
</Grid> </Grid>
<Group position="center">{pagination}</Group> <Group justify="center">{pagination}</Group>
</div> </div>
); );
} }

View File

@@ -1,12 +1,4 @@
import { import { Container, Paper, Table, Text, TextInput, Title } from "@mantine/core";
Container,
createStyles,
Paper,
Table,
Text,
TextInput,
Title,
} from "@mantine/core";
import { ReactNode, useContext, useEffect } from "react"; import { ReactNode, useContext, useEffect } from "react";
import { TitleContext } from "@/component/Header/Header"; import { TitleContext } from "@/component/Header/Header";
@@ -15,18 +7,6 @@ import store from "@/store";
import { useOptionsState } from "@/store/module/options"; import { useOptionsState } from "@/store/module/options";
import { setS3Url, setZincsearchUrl } from "@/store/reducer/options"; import { setS3Url, setZincsearchUrl } from "@/store/reducer/options";
const useStyles = createStyles((theme) => ({
settingTable: {
tableLayout: "fixed",
"& thead": {
backgroundColor:
theme.colorScheme === "dark"
? theme.colors.dark[7]
: theme.colors.gray[3],
},
},
}));
interface SettingItem { interface SettingItem {
title: string; title: string;
description: string; description: string;
@@ -34,13 +14,12 @@ interface SettingItem {
} }
export default function SettingsPage() { export default function SettingsPage() {
const { classes } = useStyles();
const [_, setTitle] = useContext(TitleContext); const [_, setTitle] = useContext(TitleContext);
const { state: options } = useOptionsState(); const { state: options } = useOptionsState();
useEffect(() => { useEffect(() => {
setTitle("Settings"); setTitle("Settings");
}, []); }, [setTitle]);
const settings: SettingItem[] = [ const settings: SettingItem[] = [
{ {
@@ -81,30 +60,28 @@ export default function SettingsPage() {
</Title> </Title>
Customize the look and feel of your Coder deployment. Customize the look and feel of your Coder deployment.
<Paper my="xl" radius="md" withBorder style={{ overflow: "hidden" }}> <Paper my="xl" radius="md" withBorder style={{ overflow: "hidden" }}>
<Table verticalSpacing="lg" className={classes.settingTable} striped> <Table verticalSpacing="lg" striped>
<thead> <Table.Thead>
<tr> <Table.Tr>
<th>Name</th> <Table.Th>Name</Table.Th>
<th>Value</th> <Table.Th>Value</Table.Th>
</tr> </Table.Tr>
</thead> </Table.Thead>
<tbody> <Table.Tbody>
{settings.map((setting) => ( {settings.map((setting) => (
<tr key={`${setting.title}`}> <Table.Tr key={`${setting.title}`}>
<td> <Table.Td>
<div> <Text size="md" fw={500}>
<Text size="md" weight={500}> {setting.title}
{setting.title} </Text>
</Text> <Text c="dimmed" size="sm">
<Text color="dimmed" size="sm"> {setting.description}
{setting.description} </Text>
</Text> </Table.Td>
</div> <Table.Td>{setting.value}</Table.Td>
</td> </Table.Tr>
<td>{setting.value}</td>
</tr>
))} ))}
</tbody> </Table.Tbody>
</Table> </Table>
</Paper> </Paper>
</Container> </Container>

View File

@@ -8,10 +8,8 @@ import MainLayout from "@/layout/MainLayout";
import SearchPage from "@/page/Search"; import SearchPage from "@/page/Search";
import store from "@/store"; import store from "@/store";
const NotFound = lazy(async () => ({ const NotFound = lazy(() => import("@/page/Exception/NotFound"));
default: (await import("@/page/Exception")).NotFoundPage, const ErrorPage = lazy(() => import("@/page/Exception/ErrorPage"));
}));
const ErrorPage = lazy(() => import("@/page/Exception"));
const LoadingPage = lazy(async () => import("@/page/Loading")); const LoadingPage = lazy(async () => import("@/page/Loading"));
const ParagraphPage = lazy(async () => import("@/page/Paragraph")); const ParagraphPage = lazy(async () => import("@/page/Paragraph"));
const SettingsPage = lazy(async () => import("@/page/Settings")); const SettingsPage = lazy(async () => import("@/page/Settings"));
@@ -25,6 +23,9 @@ const router = createHashRouter([
{ {
path: "/", path: "/",
element: <SearchPage />, element: <SearchPage />,
loader() {
return {};
},
}, },
{ {
path: "/settings", path: "/settings",
@@ -85,9 +86,9 @@ const router = createHashRouter([
const paragraph = await SearchApi.getParagraph( const paragraph = await SearchApi.getParagraph(
store.getState().options.zincsearchUrl, store.getState().options.zincsearchUrl,
id id,
).then((p) => ).then((p) =>
SearchApi.wrapParagraph(store.getState().options.s3Url, p) SearchApi.wrapParagraph(store.getState().options.s3Url, p),
); );
console.log(paragraph.markdown); console.log(paragraph.markdown);

View File

@@ -5,19 +5,21 @@ export interface OptionsState {
s3Url: string; s3Url: string;
} }
const ZINCSEARCH_URL = "https://zincsearch.yoshino-s.xyz";
const MINIO_URL = "https://minio-hdd.yoshino-s.xyz";
const optionsSlice = createSlice({ const optionsSlice = createSlice({
name: "stats", name: "stats",
initialState: { initialState: {
zincsearchUrl: "https://zincsearch.yoshino-s.xyz", zincsearchUrl: ZINCSEARCH_URL,
s3Url: "https://minio-hdd.yoshino-s.xyz", s3Url: MINIO_URL,
} as OptionsState, } as OptionsState,
reducers: { reducers: {
setZincsearchUrl: (state, action: PayloadAction<string | undefined>) => { setZincsearchUrl: (state, action: PayloadAction<string | undefined>) => {
state.zincsearchUrl = state.zincsearchUrl = action.payload ?? ZINCSEARCH_URL;
action.payload ?? "https://zincsearch.yoshino-s.xyz";
}, },
setS3Url: (state, action: PayloadAction<string | undefined>) => { setS3Url: (state, action: PayloadAction<string | undefined>) => {
state.s3Url = action.payload ?? "https://minio-hdd.yoshino-s.xyz"; state.s3Url = action.payload ?? MINIO_URL;
}, },
}, },
}); });

View File

@@ -4,5 +4,5 @@
"module": "esnext", "module": "esnext",
"moduleResolution": "node" "moduleResolution": "node"
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"],
} }

14876
yarn.lock

File diff suppressed because it is too large Load Diff