update
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
*.js
|
|
||||||
54
.eslintrc.js
54
.eslintrc.js
@@ -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
2
.gitignore
vendored
@@ -22,3 +22,5 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
.vscode
|
||||||
@@ -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
|
|
||||||
|
|||||||
18
.storybook/DocsContainer.tsx
Normal file
18
.storybook/DocsContainer.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -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
21
.vscode/settings.json
vendored
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
25
Dockerfile
25
Dockerfile
@@ -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;"]
|
|
||||||
@@ -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>
|
||||||
|
|||||||
130
package.json
130
package.json
@@ -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
12076
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
postcss.config.js
Normal file
15
postcss.config.js
Normal 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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -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 |
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/main.tsx
15
src/main.tsx
@@ -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>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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."
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
40
src/page/Exception/ErrorPage.module.css
Normal file
40
src/page/Exception/ErrorPage.module.css
Normal 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);
|
||||||
|
}
|
||||||
27
src/page/Exception/ErrorPage.tsx
Normal file
27
src/page/Exception/ErrorPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
src/page/Exception/NotFound.tsx
Normal file
11
src/page/Exception/NotFound.tsx
Normal 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."
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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),
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "node"
|
"moduleResolution": "node"
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user