From a2afd964345a761ec18fb9f4026a7043717e9011 Mon Sep 17 00:00:00 2001 From: Amateur <2863217986@qq.com> Date: Fri, 9 Aug 2024 20:51:20 +0800 Subject: [PATCH] feat: create markdown renderer and add tutorial page --- package.json | 9 ++- pnpm-lock.yaml | 126 +++++++++++++++++++++++++---- src/components/icons.tsx | 2 + src/constants/data.ts | 8 ++ src/lib/md-processor.ts | 36 +++++++++ src/pages/tutorial/index.tsx | 103 +++++++++++++++++++++++ src/pages/tutorial/md-view.tsx | 58 +++++++++++++ src/pages/tutorial/toc-sidebar.tsx | 22 +++++ src/provider/auth-provider.tsx | 47 ----------- src/router/pages.tsx | 2 + src/styles/globals.less | 28 +++++++ tailwind.config.js | 4 + 12 files changed, 379 insertions(+), 66 deletions(-) create mode 100644 src/lib/md-processor.ts create mode 100644 src/pages/tutorial/index.tsx create mode 100644 src/pages/tutorial/md-view.tsx create mode 100644 src/pages/tutorial/toc-sidebar.tsx delete mode 100644 src/provider/auth-provider.tsx diff --git a/package.json b/package.json index b0862f7..d7f6158 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,6 @@ "@radix-ui/react-tabs": "^1.1.0", "@rainbow-me/rainbowkit": "^2.1.3", "@tanstack/react-query": "^4.36.1", - "@types/history": "^5.0.0", - "@types/qs": "^6.9.15", "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -40,7 +38,10 @@ "i18next-resources-to-backend": "^1.1.4", "jotai": "^2.9.1", "js-cookie": "^3.0.5", + "lodash": "^4.17.21", "lucide-react": "^0.400.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^9.0.1", "qs": "^6.12.3", "react": "^18.3.1", "react-daisyui": "^5.0.3", @@ -49,6 +50,7 @@ "react-helmet": "^6.1.0", "react-hook-form": "^7.52.1", "react-i18next": "^13.2.2", + "react-intersection-observer": "^9.13.0", "react-markdown": "^9.0.1", "react-router-dom": "^6.10.0", "recharts": "^2.12.7", @@ -66,7 +68,10 @@ "@tanstack/react-query-devtools": "^4.29.1", "@types/js-cookie": "^3.0.6", "@types/loadable__component": "^5.13.4", + "@types/lodash": "^4.17.7", + "@types/markdown-it": "^14.1.2", "@types/node": "^18.15.11", + "@types/qs": "^6.9.15", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/react-helmet": "^6.1.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c8f943..5c3bfb9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,16 +43,10 @@ importers: version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@rainbow-me/rainbowkit': specifier: ^2.1.3 - version: 2.1.3(@tanstack/react-query@4.36.1(react-dom@18.3.1(react@18.3.1))(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(viem@2.17.11(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(wagmi@2.12.2(@tanstack/react-query@4.36.1(react-dom@18.3.1(react@18.3.1))(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1))(@types/react@18.3.3)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@3.29.4)(typescript@5.5.4)(utf-8-validate@5.0.10)(viem@2.17.11(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)) + version: 2.1.3(fibyjzvhhvdfnonpq7e3sbnwle) '@tanstack/react-query': specifier: ^4.36.1 version: 4.36.1(react-dom@18.3.1(react@18.3.1))(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) - '@types/history': - specifier: ^5.0.0 - version: 5.0.0 - '@types/qs': - specifier: ^6.9.15 - version: 6.9.15 axios: specifier: ^1.7.2 version: 1.7.3 @@ -89,9 +83,18 @@ importers: js-cookie: specifier: ^3.0.5 version: 3.0.5 + lodash: + specifier: ^4.17.21 + version: 4.17.21 lucide-react: specifier: ^0.400.0 version: 0.400.0(react@18.3.1) + markdown-it: + specifier: ^14.1.0 + version: 14.1.0 + markdown-it-anchor: + specifier: ^9.0.1 + version: 9.0.1(@types/markdown-it@14.1.2)(markdown-it@14.1.0) qs: specifier: ^6.12.3 version: 6.13.0 @@ -116,6 +119,9 @@ importers: react-i18next: specifier: ^13.2.2 version: 13.5.0(i18next@23.12.2)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) + react-intersection-observer: + specifier: ^9.13.0 + version: 9.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-markdown: specifier: ^9.0.1 version: 9.0.1(@types/react@18.3.3)(react@18.3.1) @@ -162,9 +168,18 @@ importers: '@types/loadable__component': specifier: ^5.13.4 version: 5.13.9 + '@types/lodash': + specifier: ^4.17.7 + version: 4.17.7 + '@types/markdown-it': + specifier: ^14.1.2 + version: 14.1.2 '@types/node': specifier: ^18.15.11 version: 18.19.43 + '@types/qs': + specifier: ^6.9.15 + version: 6.9.15 '@types/react': specifier: ^18.3.3 version: 18.3.3 @@ -2326,10 +2341,6 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - '@types/history@5.0.0': - resolution: {integrity: sha512-hy8b7Y1J8OGe6LbAjj3xniQrj3v6lsivCcrmf4TzSgPzLkhIeKgc5IZnT7ReIqmEuodjfO8EYAuoFvIrHi/+jQ==} - deprecated: This is a stub types definition. history provides its own type definitions, so you do not need this installed. - '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -2348,12 +2359,24 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + '@types/loadable__component@5.13.9': resolution: {integrity: sha512-QWOtIkwZqHNdQj3nixQ8oyihQiTMKZLk/DNuvNxMSbTfxf47w+kqcbnxlUeBgAxdOtW0Dh48dTAIp83iJKtnrQ==} + '@types/lodash@4.17.7': + resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} @@ -4659,6 +4682,9 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + lint-staged@13.3.0: resolution: {integrity: sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==} engines: {node: ^16.14.0 || >=18.0.0} @@ -4802,6 +4828,16 @@ packages: resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} + markdown-it-anchor@9.0.1: + resolution: {integrity: sha512-cBt7aAzmkfX8X7FqAe8EBryiKmToXgMQEEMqkXzWCm0toDtfDYIGboKeTKd8cpNJArJtutrf+977wFJTsvNGmQ==} + peerDependencies: + '@types/markdown-it': '*' + markdown-it: '*' + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + marky@1.2.5: resolution: {integrity: sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==} @@ -4835,6 +4871,9 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-query-parser@2.0.2: resolution: {integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==} @@ -5605,6 +5644,10 @@ packages: pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -5710,6 +5753,15 @@ packages: react-native: optional: true + react-intersection-observer@9.13.0: + resolution: {integrity: sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==} + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + react-dom: + optional: true + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -6563,6 +6615,9 @@ packages: ua-parser-js@1.0.38: resolution: {integrity: sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==} + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} @@ -9017,8 +9072,8 @@ snapshots: '@radix-ui/rect@1.1.0': {} - ? '@rainbow-me/rainbowkit@2.1.3(@tanstack/react-query@4.36.1(react-dom@18.3.1(react@18.3.1))(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(viem@2.17.11(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(wagmi@2.12.2(@tanstack/react-query@4.36.1(react-dom@18.3.1(react@18.3.1))(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1))(@types/react@18.3.3)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@3.29.4)(typescript@5.5.4)(utf-8-validate@5.0.10)(viem@2.17.11(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8))' - : dependencies: + '@rainbow-me/rainbowkit@2.1.3(fibyjzvhhvdfnonpq7e3sbnwle)': + dependencies: '@tanstack/react-query': 4.36.1(react-dom@18.3.1(react@18.3.1))(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) '@vanilla-extract/css': 1.14.0 '@vanilla-extract/dynamic': 2.1.0 @@ -9627,10 +9682,6 @@ snapshots: dependencies: '@types/unist': 3.0.2 - '@types/history@5.0.0': - dependencies: - history: 5.3.0 - '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -9647,14 +9698,25 @@ snapshots: '@types/json5@0.0.29': {} + '@types/linkify-it@5.0.0': {} + '@types/loadable__component@5.13.9': dependencies: '@types/react': 18.3.3 + '@types/lodash@4.17.7': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.2 + '@types/mdurl@2.0.0': {} + '@types/minimist@1.2.5': {} '@types/ms@0.7.34': {} @@ -12432,6 +12494,10 @@ snapshots: lines-and-columns@1.2.4: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + lint-staged@13.3.0: dependencies: chalk: 5.3.0 @@ -12599,6 +12665,20 @@ snapshots: map-obj@4.3.0: {} + markdown-it-anchor@9.0.1(@types/markdown-it@14.1.2)(markdown-it@14.1.0): + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.0 + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + marky@1.2.5: {} mathml-tag-names@2.1.3: {} @@ -12694,6 +12774,8 @@ snapshots: mdn-data@2.0.30: {} + mdurl@2.0.0: {} + media-query-parser@2.0.2: dependencies: '@babel/runtime': 7.25.0 @@ -13565,6 +13647,8 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 + punycode.js@2.3.1: {} + punycode@2.3.1: {} qr-code-styling@1.6.0-rc.1: @@ -13661,6 +13745,12 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-native: 0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10) + react-intersection-observer@9.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-is@16.13.1: {} react-is@17.0.2: {} @@ -14698,6 +14788,8 @@ snapshots: ua-parser-js@1.0.38: {} + uc.micro@2.1.0: {} + ufo@1.5.4: {} uint8arrays@3.1.0: diff --git a/src/components/icons.tsx b/src/components/icons.tsx index fe7d318..39b3bbe 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -9,6 +9,7 @@ import { ListChecks, LucideProps, UserCircle2, + BookOpen, } from 'lucide-react' export const Icons = { @@ -21,6 +22,7 @@ export const Icons = { users: Users, listChecks: ListChecks, profile: UserCircle2, + tutorial: BookOpen, github: ({ ...props }: LucideProps) => (
+ +
+ ) +} + +export default Tutorial diff --git a/src/pages/tutorial/md-view.tsx b/src/pages/tutorial/md-view.tsx new file mode 100644 index 0000000..5d8f845 --- /dev/null +++ b/src/pages/tutorial/md-view.tsx @@ -0,0 +1,58 @@ +import React, { useState, useEffect, useRef } from 'react' +import TocSidebar from './toc-sidebar' +import { MarkdownProcessor } from '@/lib/md-processor' +import debounce from 'lodash/debounce' +interface IMarkdownRendererProps extends React.HTMLAttributes { + content: string +} + +const MdView: React.FC = ({ content, ...props }) => { + const mdProcessor = new MarkdownProcessor(content) + const [activeId, setActiveId] = useState('') + const contentRef = useRef(null) + + const debouncedSetActiveId = debounce((id: string) => { + setActiveId(id) + }, 100) // 100ms 去抖动时间 + + useEffect(() => { + if (!contentRef.current) return + + const observerOptions = { + root: null, + rootMargin: '0px 0px -70% 0px', + threshold: 1, + } + + const observerCallback: IntersectionObserverCallback = (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + debouncedSetActiveId(entry.target.id) + break + } + } + } + + const observer = new IntersectionObserver(observerCallback, observerOptions) + const elements = contentRef.current.querySelectorAll('h1, h2, h3') as NodeListOf + elements.forEach((element) => observer.observe(element)) + + return () => observer.disconnect() + }, [debouncedSetActiveId]) + + return ( +
+
+
+
+ +
+ ) +} + +export default MdView diff --git a/src/pages/tutorial/toc-sidebar.tsx b/src/pages/tutorial/toc-sidebar.tsx new file mode 100644 index 0000000..134c56a --- /dev/null +++ b/src/pages/tutorial/toc-sidebar.tsx @@ -0,0 +1,22 @@ +import React from 'react' + +interface TocSidebarProps { + toc: { id: string; title: string; slug: string }[] + activeId: string +} + +const TocSidebar: React.FC = ({ toc, activeId }) => { + return ( + + ) +} + +export default TocSidebar diff --git a/src/provider/auth-provider.tsx b/src/provider/auth-provider.tsx deleted file mode 100644 index 7a81972..0000000 --- a/src/provider/auth-provider.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import Cookies from 'js-cookie' -import { ReactNode, createContext, useContext, useMemo, useState } from 'react' - -interface AuthProviderProps { - children: ReactNode -} - -// 定义 AuthContext 的类型 -interface AuthContextType { - token: string | null - setToken: (token: string | null) => void -} - -const AuthContext = createContext(undefined) - -const AuthProvider = ({ children }: AuthProviderProps) => { - // State to hold the authentication token - const [token, setToken_] = useState(Cookies.get('token') || null) - - // Function to set the authentication token - const setToken = (newToken: string | null) => { - setToken_(newToken) - } - - // Memoized value of the authentication context - const contextValue = useMemo( - () => ({ - token, - setToken, - }), - [token], - ) - - // Provide the authentication context to the children components - return {children} -} - -// 使用 useAuth hook 来访问 AuthContext -export const useAuth = (): AuthContextType => { - const context = useContext(AuthContext) - if (context === undefined) { - throw new Error('useAuth must be used within an AuthProvider') - } - return context -} - -export default AuthProvider diff --git a/src/router/pages.tsx b/src/router/pages.tsx index 10b2aae..95f94ee 100644 --- a/src/router/pages.tsx +++ b/src/router/pages.tsx @@ -5,6 +5,7 @@ import Login from '@/pages/login' import Callback from '@/pages/callback' import Profile from '@/pages/profile' import MyTask from '@/pages/mytask' +import Tutorial from '@/pages/tutorial' export const Pages = { dashboard: Dashboard, @@ -14,4 +15,5 @@ export const Pages = { login: Login, callback: Callback, profile: Profile, + tutorial: Tutorial, } diff --git a/src/styles/globals.less b/src/styles/globals.less index 8c513f4..a6d3e74 100644 --- a/src/styles/globals.less +++ b/src/styles/globals.less @@ -2,6 +2,13 @@ @tailwind components; @tailwind utilities; +@font-face { + font-family: MyCustomFont; + src: url('/fonts/MyCustomFont.woff2') format('woff2'), url('/fonts/MyCustomFont.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + @layer base { :root { --background: 0 0% 100%; @@ -68,3 +75,24 @@ @apply bg-background text-foreground; } } + +@layer components { + .toc-h1 a { + @apply ml-1 block font-medium text-slate-300 hover:text-purple-700; + } + + .toc-h2 a { + @apply ml-4 items-start text-slate-300 hover:text-purple-700; + } + + .toc-h3 a { + @apply ml-8 items-start text-slate-300 hover:text-purple-700; + } + + .toc-item.active { + @apply font-bold; + + background: linear-gradient(to right, rgb(128 0 128 / 50%), rgb(128 0 128 / 20%)); + border-radius: 8px; + } +} diff --git a/tailwind.config.js b/tailwind.config.js index be78f53..a7a3f1f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,6 +2,7 @@ module.exports = { darkMode: ['class'], content: ['./src/**/*.{ts,tsx}'], + safelist: ['toc-h1', 'toc-h2', 'toc-h3', 'active', 'toc-item'], theme: { container: { center: true, @@ -11,6 +12,9 @@ module.exports = { }, }, extend: { + fontFamily: { + custom: ['MyCustomFont', 'sans-serif'], + }, colors: { border: 'hsl(var(--border))', input: 'hsl(var(--input))',