diff --git a/README.md b/README.md index dcdf515..3fbf132 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,7 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +## 小破站 -## Getting Started +一个个人博客,Powered by [Next.js](https://nextjs.org/). -First, run the development server: +## 施工中 -```bash -npm run dev -# or -yarn dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. - -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. - -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. - -## Todo - -- [ ] Add types https://github1s.com/vercel/next.js/blob/canary/examples/blog-starter-typescript/ \ No newline at end of file +- [x] 归档页面 diff --git a/app/store.ts b/app/store.ts index 79d3d8a..d7be8ca 100644 --- a/app/store.ts +++ b/app/store.ts @@ -1,4 +1,4 @@ -import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; +import { configureStore } from '@reduxjs/toolkit'; import routerReducer from '../features/router/routerSlice'; export const store = configureStore({ diff --git a/components/post/CopyButton.tsx b/components/post/CopyButton.tsx new file mode 100644 index 0000000..9d698ae --- /dev/null +++ b/components/post/CopyButton.tsx @@ -0,0 +1,39 @@ +import { Button, useClipboard } from '@chakra-ui/react'; +import { FC, MouseEventHandler, useEffect, useState } from 'react'; + +const CopyButton: FC = ({ children }) => { + // Copy code + const [codeContent, setCodeContent] = useState(''); + const { hasCopied, onCopy } = useClipboard(codeContent); + + const copyCode: MouseEventHandler = (e) => { + const target = e.target as HTMLButtonElement; + // Button is sibling with Code tag + const codeToCopy = target.nextElementSibling?.textContent; + codeToCopy && setCodeContent(codeToCopy); + }; + useEffect(() => { + codeContent && onCopy(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [codeContent]); + + return ( + <> +
+        
+        {children}
+      
+ + ); +}; + +export default CopyButton; diff --git a/lib/posts.ts b/lib/posts.ts index 0d15ac0..93799ec 100644 --- a/lib/posts.ts +++ b/lib/posts.ts @@ -1,7 +1,8 @@ import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; -import markdownToTxt from 'markdown-to-txt'; +import { remark } from 'remark'; +import strip from 'strip-markdown'; const postsDirectory = path.join(process.cwd(), 'public/posts'); @@ -23,34 +24,38 @@ export interface AllPostsData extends MyMatters { * Get all sorted posts * @returns */ -export function getSortedPostsData() { +export async function getSortedPostsData() { // Get file names under /posts const fileNames = fs.readdirSync(postsDirectory); - const allPostsData = fileNames.map((fileName) => { - // Remove ".md" from file name to get id - const id = fileName.replace(/\.md$/, ''); + const allPostsData = await Promise.all( + fileNames.map(async (fileName) => { + // Remove ".md" from file name to get id + const id = fileName.replace(/\.md$/, ''); - // Read markdown file as string - const fullPath = path.join(postsDirectory, fileName); - const fileContents = fs.readFileSync(fullPath, 'utf8'); + // Read markdown file as string + const fullPath = path.join(postsDirectory, fileName); + const fileContents = fs.readFileSync(fullPath, 'utf8'); - // Use gray-matter to parse the post metadata section - const matterResult = matter(fileContents); + // Use gray-matter to parse the post metadata section + const matterResult = matter(fileContents); - // Process markdown to plain text - const contentText = markdownToTxt(matterResult.content); + // Process markdown to plain text + const contentText = await remark() + .use(strip) + .process(matterResult.content); - // Combine the data with the id - return { - id, - // Add post description - desc: `${contentText.slice(0, 100)}...`, - ...({ - ...matterResult.data, - date: matterResult.data.date.toISOString(), - } as MyMatters), - }; - }); + // Combine the data with the id + return { + id, + // Add post description + desc: `${contentText.toString().slice(0, 100)}...`, + ...({ + ...matterResult.data, + date: matterResult.data.date.toISOString(), + } as MyMatters), + }; + }) + ); // Sort posts by date return allPostsData.sort(({ date: a }, { date: b }) => { @@ -62,12 +67,6 @@ export function getSortedPostsData() { return 0; } }); - - // Convert Date to locale string. - // return allPostsData.map((post) => { - // if (typeof post.date == 'object') post.date = post.date.toISOString(); - // return post; - // }); } /** @@ -119,7 +118,7 @@ export function getPagingData(allPostsData: AllPostsData[], start?: string) { }; } -export function getAllPostSlugs() { +export async function getAllPostSlugs() { const fileNames = fs.readdirSync(postsDirectory); // Returns an array that looks like this: @@ -135,18 +134,20 @@ export function getAllPostSlugs() { // } // } // ] - return fileNames.map((fileName) => { - const allPostsData = getSortedPostsData(); - const slug = allPostsData.find( - (item) => item.id === fileName.replace(/\.md$/, '') - ); + return await Promise.all( + fileNames.map(async (fileName) => { + const allPostsData = await getSortedPostsData(); + const slug = allPostsData.find( + (item) => item.id === fileName.replace(/\.md$/, '') + ); - return { - params: { - slug: slug?.url, - }, - }; - }); + return { + params: { + slug: slug?.url, + }, + }; + }) + ); } export interface MyPost extends AllPostsData { @@ -154,7 +155,7 @@ export interface MyPost extends AllPostsData { } export async function getPostData(slug: string) { - const allPostsData = getSortedPostsData(); + const allPostsData = await getSortedPostsData(); const post = allPostsData.find((item) => item.url === slug); const fullPath = path.join(postsDirectory, `${post?.id}.md`); diff --git a/package.json b/package.json index c151778..8e3b5e8 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "framer-motion": "^5.0.2", "gray-matter": "^4.0.3", "highlight.js": "^11.3.1", - "markdown-to-txt": "^1.0.1", "next": "11.1.2", "postcss": "^8.3.11", "react": "17.0.2", @@ -44,6 +43,7 @@ "remark-parse": "^10.0.0", "remark-rehype": "^10.0.1", "remark-toc": "^8.0.1", + "strip-markdown": "^5.0.0", "unified": "^10.1.0" }, "devDependencies": { diff --git a/pages/_app.tsx b/pages/_app.tsx index b829d12..c25e665 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -7,6 +7,7 @@ import { Provider } from 'react-redux'; import { store } from '../app/store'; import 'antd/dist/antd.css'; import '../assets/css/rua.css'; +import Head from 'next/head'; const theme = extendTheme({ colors: { @@ -35,11 +36,17 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? ((page) => page); return ( - - - {getLayout()} - - + <> + + + + + + + {getLayout()} + + + ); } export default MyApp; diff --git a/pages/about.tsx b/pages/about.tsx new file mode 100644 index 0000000..5cdf2c5 --- /dev/null +++ b/pages/about.tsx @@ -0,0 +1,31 @@ +import { Box } from '@chakra-ui/react'; +import Head from 'next/head'; +import { ReactElement } from 'react'; +import HomeLayout from '../layouts/HomeLayout'; + +const about = () => { + return ( + <> + + RUA - About + + + + 施工中... + + + ); +}; + +about.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default about; diff --git a/pages/archive.tsx b/pages/archive.tsx index ccc0f74..dd4e3ba 100644 --- a/pages/archive.tsx +++ b/pages/archive.tsx @@ -7,7 +7,7 @@ import { getArchiveData, getSortedPostsData } from '../lib/posts'; import ArchiveCard from '../components/ArchiveCard'; export const getStaticProps = async () => { - const allPostsData = getSortedPostsData(); + const allPostsData = await getSortedPostsData(); return { props: { diff --git a/pages/index.tsx b/pages/index.tsx index 005fe0d..38270af 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -7,7 +7,7 @@ import HomeLayout from '../layouts/HomeLayout'; import PostCard from '../components/PostCard'; export const getStaticProps = async () => { - const allPostsData = getSortedPostsData(); + const allPostsData = await getSortedPostsData(); return { props: { diff --git a/pages/posts/[slug].tsx b/pages/posts/[slug].tsx index 13786f6..ffbc1a4 100644 --- a/pages/posts/[slug].tsx +++ b/pages/posts/[slug].tsx @@ -10,13 +10,7 @@ import remarkParse from 'remark-parse'; import remarkGfm from 'remark-gfm'; import rehypeHighlight from 'rehype-highlight'; import { unified } from 'unified'; -import { - createElement, - Fragment, - MouseEventHandler, - useEffect, - useState, -} from 'react'; +import { createElement, Fragment } from 'react'; import { Box, Image, @@ -25,7 +19,6 @@ import { Icon, Link, Button, - useClipboard, Tag, } from '@chakra-ui/react'; import 'highlight.js/styles/github.css'; @@ -41,9 +34,10 @@ import { Giscus } from '@giscus/react'; import { RootState } from '../../app/store'; import { useSelector, useDispatch } from 'react-redux'; import { cleanFromPath } from '../../features/router/routerSlice'; +import CopyButton from '../../components/post/CopyButton'; export async function getStaticPaths() { - const paths = getAllPostSlugs(); + const paths = await getAllPostSlugs(); return { paths, fallback: false, @@ -66,23 +60,9 @@ const Post = ({ postData }: InferGetStaticPropsType) => { const goBack = () => { dispatch(cleanFromPath()); - router.push(fromPath); + router.push(fromPath || '/'); }; - // Copy code - const [codeContent, setCodeContent] = useState(''); - const { hasCopied, onCopy } = useClipboard(codeContent); - const copyCode: MouseEventHandler = (e) => { - const target = e.target as HTMLButtonElement; - // Button is sibling with Code tag - const codeToCopy = target.nextElementSibling?.textContent; - codeToCopy && setCodeContent(codeToCopy); - }; - useEffect(() => { - codeContent && onCopy(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [codeContent]); - const processedContent = unified() .use(remarkParse) .use(remarkToc, { @@ -116,23 +96,7 @@ const Post = ({ postData }: InferGetStaticPropsType) => { ); }, - pre: (props: any) => { - return ( -
-              
-              {props.children}
-            
- ); - }, + pre: CopyButton, }, Fragment, }); @@ -165,6 +129,7 @@ const Post = ({ postData }: InferGetStaticPropsType) => { flexFlow={['column', 'column', 'row']} px="0.5rem" > + {/* Back button */} + {/* Main article area */} ) => { overflow="hidden" flex="1" > + {/* Post image */} {postData.index_img && ( ) => { alt="Head image" /> )} + + {/* Post heading */} {postData.title} + {/* Post tags */} {Array.isArray(postData.tags) ? // Mutil tags @@ -222,12 +192,14 @@ const Post = ({ postData }: InferGetStaticPropsType) => { + {/* Post content */} {postContent} + {/* Comment */} ) => {