diff --git a/next.config.js b/next.config.js index a843cbe..c34d150 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,8 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + // basePath: process.env.NODE_ENV === "production" ? "/web" : "/debug", + basePath: "/web", reactStrictMode: true, -} +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/package.json b/package.json index caefaea..ab1d7a3 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,30 @@ }, "dependencies": { "@next/font": "13.1.6", + "@tanstack/react-query": "^4.24.6", "@types/node": "18.13.0", "@types/react": "18.0.28", "@types/react-dom": "18.0.10", + "clsx": "^1.2.1", "eslint": "8.34.0", "eslint-config-next": "13.1.6", "next": "13.1.6", "react": "18.2.0", "react-dom": "18.2.0", + "react-markdown": "^8.0.5", + "react-syntax-highlighter": "^15.5.0", + "react-use": "^17.4.0", + "rehype-highlight": "^6.0.0", + "rehype-pretty-code": "^0.9.4", + "remark-gfm": "^3.0.1", + "shiki": "^0.14.1", + "styled-components": "^5.3.8", "typescript": "4.9.5" + }, + "devDependencies": { + "@types/styled-components": "^5.1.26", + "autoprefixer": "^10.4.13", + "postcss": "^8.4.21", + "tailwindcss": "^3.2.6" } } diff --git a/pages/index.tsx b/pages/index.tsx index 3a20955..5d16933 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,123 +1,524 @@ -import Head from 'next/head' -import Image from 'next/image' -import { Inter } from '@next/font/google' -import styles from '@/styles/Home.module.css' +import Head from "next/head"; +import Image from "next/image"; +import { Inter } from "@next/font/google"; +import styles from "@/styles/Home.module.css"; +import { + QueryClientProvider, + QueryClient, + useQuery, + useMutation, +} from "@tanstack/react-query"; +import { useCallback, useEffect, useState } from "react"; +import ReactMarkdown from "react-markdown"; +import { useKey } from "react-use"; +import cn from "clsx"; +// import rehypePrettyCode from "rehype-pretty-code"; +// import * as shiki from "shiki"; +import remarkGfm from "remark-gfm"; +import styled from "styled-components"; +// import rehypeHighlight from "rehype-highlight"; -const inter = Inter({ subsets: ['latin'] }) +// @ts-ignore +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +// @ts-ignore +import theme from "react-syntax-highlighter/dist/cjs/styles/prism/vs-dark"; +// const CDN_BASE = "https://npm.elemecdn.com/"; +// shiki.setCDN(`${CDN_BASE}/shiki@0.14.1/`); -export default function Home() { +const SMD = styled(ReactMarkdown)` + blockquote, + hr, + p { + margin-block: 1rem; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + font-weight: bold; + margin-block: 1rem; + } + h1 { + font-size: 3rem; + color: var(--h1-color); + } + h2 { + font-size: 2.5rem; + color: var(--h2-color); + } + h3 { + font-size: 2rem; + color: var(--h3-color); + } + h4 { + font-size: 1.5rem; + color: var(--h4-color); + } + h5 { + font-size: 1rem; + color: var(--h5-color); + } + h6 { + font-size: 0.9rem; + color: var(--h6-color); + } + + img { + display: block; + margin-left: auto; + margin-right: auto; + } + + .img-alt { + display: block; + margin: 0 0 1rem 0; + font-size: 16px; + text-align: center; + } + + th { + font-weight: 600; + } + + thead { + border-bottom: 2px solid var(--background-modifier-border); + } + + tr { + line-height: normal; + padding: 0 4px; + background-color: var(--pre-code); + } + + tr:nth-child(0) { + padding-top: 4px; + } + + th, + td { + padding: 0.5em 1em; + } + + td { + border-bottom: 1px solid var(--background-modifier-border); + } + td:not(:last-child) { + border-right: 1px solid var(--background-modifier-border); + } + + strong { + font-weight: 600; + } + + a { + color: var(--text-a); + text-decoration: none; + } + + a:hover { + color: var(--text-a-hover); + text-decoration: none; + } + + blockquote { + margin: 1rem 0; + padding-inline: 2ch; + padding-block: 0.5rem; + background-color: var(--pre-code); + border-left: 0.5ch solid var(--interactive-accent); + } + blockquote:has(> blockquote) { + padding-bottom: 0; + } + blockquote > blockquote { + margin-bottom: 0; + } + blockquote:not(:has(> blockquote)) { + margin-bottom: 1rem; + } + + hr { + background-color: var(--background-modifier-border); + height: 1px; + border: 0; + } + + ul { + list-style-type: revert; + } + ol { + list-style-type: decimal; + } + ul:not(.contains-task-list), + ol:not(.contains-task-list) { + padding-left: 2em; + } + ul.contains-task-list, + ol.contains-task-list { + margin-left: 0; + list-style-type: none; + } +`; + +const TextTyper = ({ + // now the phrase, interval and HTML element desired will come via props and we have some default values here + text = "", + skip = 0, + onFinish = () => {}, +}) => { + const [typedText, setTypedText] = useState(""); + const interval = 50; + const step = Math.ceil(text.length / 100); + + // @ts-ignore + const typingRender = (text, updater, interval) => { + let localTypingIndex = skip; + let localTyping = text.slice(0, skip); + if (text) { + let printer = setInterval(() => { + if (localTypingIndex < text.length) { + updater( + (localTyping += text.slice( + localTypingIndex, + localTypingIndex + step + )) + ); + localTypingIndex += step; + document.querySelector("#anchor")?.scrollIntoView(); + } else { + localTypingIndex = 0; + localTyping = ""; + clearInterval(printer); + onFinish(); + } + }, interval); + } + }; + useEffect(() => { + typingRender(text, setTypedText, interval); + }, [text, interval]); + + return
+ {children}
+
+ );
+ },
+};
+const Markdown = ({ children }: { children: string }) => {
+ return (
+