Create blog with nextjs
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
git
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
db.json
|
||||||
|
public
|
||||||
|
node_modules
|
6
.eslintrc.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": ["next", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
"react-hooks/rules-of-hooks": "off"
|
||||||
|
}
|
||||||
|
}
|
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
29
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Next.js: debug server-side",
|
||||||
|
"type": "node-terminal",
|
||||||
|
"request": "launch",
|
||||||
|
"command": "yarn dev"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: debug client-side",
|
||||||
|
"type": "pwa-msedge",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "http://localhost:3000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: debug full stack",
|
||||||
|
"type": "node-terminal",
|
||||||
|
"request": "launch",
|
||||||
|
"command": "yarn dev",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"serverReadyAction": {
|
||||||
|
"pattern": "started server on .+, url: (https?://.+)",
|
||||||
|
"uriFormat": "%s",
|
||||||
|
"action": "debugWithChrome"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
FROM node:16-alpine as builder
|
||||||
|
WORKDIR /root
|
||||||
|
COPY ./ ./
|
||||||
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||||
|
&& apk update --no-cache \
|
||||||
|
&& apk upgrade --no-cache \
|
||||||
|
&& yarn config set registry https://registry.npm.taobao.org \
|
||||||
|
&& yarn \
|
||||||
|
&& yarn build
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
WORKDIR /root
|
||||||
|
COPY --from=builder /root/dist /usr/share/nginx/html
|
38
README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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/
|
841
assets/css/rua.css
Normal file
@ -0,0 +1,841 @@
|
|||||||
|
:root {
|
||||||
|
--blcqd-bg-color: #f8f8f8;
|
||||||
|
--shadow-color: #c3c5c7;
|
||||||
|
--checkbox-color: #f8a9b5;
|
||||||
|
--checked-font-color: #eee;
|
||||||
|
--bg-color: #ffffff; /*改变背景色*/
|
||||||
|
--text-color: #333333; /*改变文字颜色*/
|
||||||
|
--md-char-color: #c7c5c5; /*改变元字符的颜色,例如 markdown 中的“*”*/
|
||||||
|
--meta-content-color: #c7c5c5; /*改变元内容的颜色,例如 markdown 中的图像文本和链接地址*/
|
||||||
|
|
||||||
|
--primary-color: #428bca; /*主按钮的颜色*/
|
||||||
|
--primary-btn-border-color: #285e8e;
|
||||||
|
--primary-btn-text-color: #fff;
|
||||||
|
|
||||||
|
--window-border: 1px solid #eee; /*边栏等的边框*/
|
||||||
|
|
||||||
|
--active-file-bg-color: #eee; /*文件树或文件列表中当前项的背景色*/
|
||||||
|
--active-file-text-color: inherit;
|
||||||
|
--active-file-border-color: #777;
|
||||||
|
|
||||||
|
--side-bar-bg-color: var(--bg-color); /*改变边栏颜色*/
|
||||||
|
--item-hover-bg-color: rgba(
|
||||||
|
229,
|
||||||
|
229,
|
||||||
|
229,
|
||||||
|
0.59
|
||||||
|
); /*鼠标悬停时控件项的背景,如侧边栏中的菜单*/
|
||||||
|
--item-hover-text-color: inherit;
|
||||||
|
--monospace: monospace; /*代码的等宽字体*/
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
color: unset;
|
||||||
|
background: rgba(33, 150, 243, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*font-family*/
|
||||||
|
|
||||||
|
/* @font-face {
|
||||||
|
font-family: 'Quicksand';
|
||||||
|
src: url('ava-diana/Quicksand/Quicksand-Regular.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Quicksand-bold';
|
||||||
|
src: url('ava-diana/Quicksand/Quicksand-Bold.ttf');
|
||||||
|
} */
|
||||||
|
|
||||||
|
html, body,
|
||||||
|
#write {
|
||||||
|
/* font-family: "Quicksand",'Arial Hebrew Scholar'; */
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOC */
|
||||||
|
#table-of-contents {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
line-height: 2rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #797979;
|
||||||
|
}
|
||||||
|
#table-of-contents + ul {
|
||||||
|
padding-left: 30px;
|
||||||
|
margin: 16px;
|
||||||
|
max-height: 40rem;
|
||||||
|
overflow: auto;
|
||||||
|
scrollbar-width: 0;
|
||||||
|
}
|
||||||
|
#table-of-contents + ul li {
|
||||||
|
list-style-type: none;
|
||||||
|
position: relative;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
#table-of-contents + ul li ul {
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
#table-of-contents + ul li::before {
|
||||||
|
content: ' - ';
|
||||||
|
/* font-family: 'Quicksand-bold'; */
|
||||||
|
position: absolute;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #aabfc5;
|
||||||
|
transform: translate(-1.4rem, -1rem);
|
||||||
|
}
|
||||||
|
#table-of-contents + ul li li::before {
|
||||||
|
content: ' · ';
|
||||||
|
/* font-family: 'Quicksand-bold'; */
|
||||||
|
position: absolute;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #aabfc5;
|
||||||
|
transform: translate(-1rem, -0.75rem);
|
||||||
|
}
|
||||||
|
#table-of-contents + ul::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*YAML Front Matter*/
|
||||||
|
#write pre.md-meta-block {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
min-height: 30px;
|
||||||
|
background-image: linear-gradient(177deg, #fbebef, #dff0f5);
|
||||||
|
padding: 1.5em;
|
||||||
|
padding-left: calc(1000px + 1.5em);
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 1em;
|
||||||
|
padding-bottom: 1.5em;
|
||||||
|
padding-top: 3em;
|
||||||
|
margin-top: -1.5em;
|
||||||
|
color: rgba(12, 12, 12, 0.4);
|
||||||
|
margin-left: -1000px;
|
||||||
|
margin-right: -1000px;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
font-family: var(--monospace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#write pre.md-meta-block::selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #B8A6D9;
|
||||||
|
color: hsla(81, 40%, 75%, 1);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*headings*/
|
||||||
|
#write h1 {
|
||||||
|
font-size: 3.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
line-height: 4rem;
|
||||||
|
margin: 0 0 1.75rem;
|
||||||
|
padding: 20px 30px;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 4rem;
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
color: #464b1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write h2 {
|
||||||
|
font-size: 1.999rem;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
line-height: 2rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
/*text-transform: uppercase;*/
|
||||||
|
color: #464b1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write h3 {
|
||||||
|
font-size: 1.413rem;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
line-height: 3rem;
|
||||||
|
/*text-transform: uppercase;*/
|
||||||
|
/* margin-left: 1rem; */
|
||||||
|
color: #464b1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write h4 {
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
/*text-transform: uppercase;*/
|
||||||
|
margin-top: 1rem;
|
||||||
|
/* margin-left: 3rem; */
|
||||||
|
color: #464b1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write h5 {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
/*text-transform: uppercase;*/
|
||||||
|
color: #464b1f;
|
||||||
|
/* margin-left: 3.5rem; */
|
||||||
|
}
|
||||||
|
|
||||||
|
#write h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
/*text-transform: uppercase;*/
|
||||||
|
/* margin-left: 4rem; */
|
||||||
|
color: #464b1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write p {
|
||||||
|
margin: 1rem 0;
|
||||||
|
line-height: 1.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*blockquote*/
|
||||||
|
#write blockquote::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: calc(0.1rem + 2.43px);
|
||||||
|
height: 100%;
|
||||||
|
transform: translateX(calc(-0.05rem - 1.215px));
|
||||||
|
background-image: linear-gradient(#fbebef, #dff0f5);
|
||||||
|
border-radius: calc(0.5rem + 0.25%) 0 0 calc(0.5rem + 0.25%);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 575px) {
|
||||||
|
#write blockquote::before {
|
||||||
|
width: calc(0.1rem + 0.5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#write blockquote {
|
||||||
|
position: relative;
|
||||||
|
word-spacing: 0.06em;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 1rem calc(0.5rem + 5.13px);
|
||||||
|
/*border-left: solid .5rem var(--select-text-bg-color);*/
|
||||||
|
/*border-radius: .5rem 1rem 1rem .5rem;*/
|
||||||
|
background-color: var(--blcqd-bg-color);
|
||||||
|
line-height: 1.6;
|
||||||
|
/*box-shadow: 3px 3px 3px 1px var(--shadow-color);*/
|
||||||
|
border-radius: 0 calc(0.5rem + 0.25%) calc(0.5rem + 0.25%) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 575px) {
|
||||||
|
#write blockquote {
|
||||||
|
padding: 1rem calc(0.5rem + 1%);
|
||||||
|
border-radius: 0 calc(0.5rem + 0.25%) calc(0.5rem + 0.25%) 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#write blockquote::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
/*box-shadow: -3px 3px 3px 1px var(--shadow-color);*/
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write blockquote blockquote {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write blockquote p {
|
||||||
|
margin: unset;
|
||||||
|
line-height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*list*/
|
||||||
|
|
||||||
|
#write ul,
|
||||||
|
#write ol {
|
||||||
|
padding-left: 30px;
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write ul li {
|
||||||
|
list-style-type: none;
|
||||||
|
position: relative;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write ul li::before {
|
||||||
|
content: ' - ';
|
||||||
|
/* font-family: 'Quicksand-bold'; */
|
||||||
|
position: absolute;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #aabfc5;
|
||||||
|
transform: translate(-1.4rem, -1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#write ul li li::before {
|
||||||
|
content: ' · ';
|
||||||
|
/* font-family: 'Quicksand-bold'; */
|
||||||
|
position: absolute;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #aabfc5;
|
||||||
|
transform: translate(-1rem, -0.75rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*task list*/
|
||||||
|
#write .md-task-list-item {
|
||||||
|
transition: all 0.2s ease-in-out 0s;
|
||||||
|
transform: translateX(1.4rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#write .md-task-list-item::before {
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
#write input + p {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write input[type='checkbox'] {
|
||||||
|
appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write input[type='checkbox']::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(calc(-1.1rem - 10px), calc(-1rem));
|
||||||
|
/*transform: translate(0px,calc(-1rem));*/
|
||||||
|
height: 1.4rem;
|
||||||
|
width: 1.4rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0.15rem;
|
||||||
|
background-color: white;
|
||||||
|
background-clip: content-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: solid 0.15em #c3c4d0;
|
||||||
|
/*background-color: deepskyblue;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#write input[type='checkbox']:checked::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
/*transform: translate(-10px,70%);*/
|
||||||
|
height: 1.4rem;
|
||||||
|
width: 1.4rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0.15rem;
|
||||||
|
background-color: #a8c8da;
|
||||||
|
background-clip: content-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: solid 0.15rem #a8c8da;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write input[type='checkbox'] + p {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write input[type='checkbox']:checked + p {
|
||||||
|
color: #c3c4d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write .md-task-list-item:hover {
|
||||||
|
color: #c3c4d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*fences*/
|
||||||
|
/* Based on https://github.com/dempfi/ayu */
|
||||||
|
|
||||||
|
/*.cm-s-inner.CodeMirror { background: #f8f8f8; color: #4f4652; }
|
||||||
|
.cm-s-inner div.CodeMirror-selected { background: #cbccc6; }
|
||||||
|
.cm-s-inner .CodeMirror-line::selection, .cm-s-inner .CodeMirror-line > span::selection, .cm-s-inner .CodeMirror-line > span > span::selection { background: #34455a; }
|
||||||
|
.cm-s-inner .CodeMirror-line::-moz-selection, .cm-s-inner .CodeMirror-line > span::-moz-selection, .cm-s-inner .CodeMirror-line > span > span::-moz-selection { background: rgba(25, 30, 42, 99); }
|
||||||
|
.cm-s-inner .CodeMirror-gutters { background: #f8f8f8; border-right: 0px; border-right: 1px solid #ddd}
|
||||||
|
.cm-s-inner .CodeMirror-guttermarker { color: white; }
|
||||||
|
.cm-s-inner .CodeMirror-guttermarker-subtle { color: rgba(112, 122, 140, 66); }
|
||||||
|
.cm-s-inner .CodeMirror-linenumber { color: #cbccc6; }
|
||||||
|
.cm-s-inner .CodeMirror-cursor { border-left: 2px solid #e56769;}
|
||||||
|
.cm-s-inner div.CodeMirror-cursor { border-left: 2px solid #e56769;}
|
||||||
|
.cm-s-inner. cm-fat-cursor .CodeMirror-cursor {background-color: #a2a8a175 !important;}
|
||||||
|
.cm-s-inner .cm-animate-fat-cursor { background-color: #a2a8a175 !important; }
|
||||||
|
|
||||||
|
.cm-s-inner span.cm-comment { color: #7e7e78; font-style:italic; }
|
||||||
|
.cm-s-inner span.cm-atom { color: #ae81ff; }
|
||||||
|
.cm-s-inner span.cm-number { color: #ffa600; }
|
||||||
|
|
||||||
|
.cm-s-inner span.cm-comment.cm-attribute { color: #ffd580; }
|
||||||
|
.cm-s-inner span.cm-comment.cm-def { color: #9566fa; }
|
||||||
|
.cm-s-inner span.cm-comment.cm-tag { color: #5ccfe6; }
|
||||||
|
.cm-s-inner span.cm-comment.cm-type { color: #5998a6; }
|
||||||
|
|
||||||
|
.cm-s-inner span.cm-property { color: #f29e74; }
|
||||||
|
.cm-s-inner span.cm-attribute { color: #ffd580; }
|
||||||
|
.cm-s-inner span.cm-keyword { color: #ffa759; }
|
||||||
|
.cm-s-inner span.cm-builtin { color: #ffcc66; }
|
||||||
|
.cm-s-inner span.cm-string { color: #008700; }
|
||||||
|
|
||||||
|
.cm-s-inner span.cm-variable { color: #97009c; }
|
||||||
|
.cm-s-inner span.cm-variable-2 { color: #f28779; }
|
||||||
|
.cm-s-inner span.cm-variable-3 { color: #5ccfe6; }
|
||||||
|
.cm-s-inner span.cm-type { color: #ff8620; }
|
||||||
|
.cm-s-inner span.cm-def { color: #fcbb3c; }
|
||||||
|
.cm-s-inner span.cm-bracket { color: rgba(92, 207, 230, 80); }
|
||||||
|
.cm-s-inner span.cm-tag { color: #5ccfe6; }
|
||||||
|
.cm-s-inner span.cm-header { color: #bae67e; }
|
||||||
|
.cm-s-inner span.cm-link { color: #5ccfe6; }
|
||||||
|
.cm-s-inner span.cm-error { color: #ff3333; }*/
|
||||||
|
|
||||||
|
.cm-s-inner.CodeMirror {
|
||||||
|
color: #999;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.cm-s-inner div.CodeMirror-selected {
|
||||||
|
background: #cfc;
|
||||||
|
}
|
||||||
|
.cm-s-inner .CodeMirror-line::selection,
|
||||||
|
.cm-s-inner .CodeMirror-line > span::selection,
|
||||||
|
.cm-s-inner .CodeMirror-line > span > span::selection {
|
||||||
|
background: #cfc;
|
||||||
|
}
|
||||||
|
.cm-s-inner .CodeMirror-line::-moz-selection,
|
||||||
|
.cm-s-inner .CodeMirror-line > span::-moz-selection,
|
||||||
|
.cm-s-inner .CodeMirror-line > span > span::-moz-selection {
|
||||||
|
background: #cfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-s-inner .CodeMirror-gutters {
|
||||||
|
/*background: #f8f8f8; */
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.cm-s-inner .CodeMirror-linenumber {
|
||||||
|
color: #aaa;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
.cm-s-inner .CodeMirror-cursor {
|
||||||
|
border-left: 2px solid #e56769;
|
||||||
|
}
|
||||||
|
.cm-s-inner div.CodeMirror-cursor {
|
||||||
|
border-left: 2px solid #e56769;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-s-inner .cm-keyword {
|
||||||
|
color: #313de2;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-atom {
|
||||||
|
color: #f90;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-number {
|
||||||
|
color: #ca7841;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-def {
|
||||||
|
color: #8da6ce;
|
||||||
|
}
|
||||||
|
.cm-s-inner span.cm-variable-2,
|
||||||
|
.cm-s-inner span.cm-tag {
|
||||||
|
color: #690;
|
||||||
|
}
|
||||||
|
.cm-s-inner span.cm-variable-3,
|
||||||
|
.cm-s-inner span.cm-def,
|
||||||
|
.cm-s-inner span.cm-type {
|
||||||
|
color: #07a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-s-inner .cm-variable {
|
||||||
|
color: #07a;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-property {
|
||||||
|
color: #905;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-qualifier {
|
||||||
|
color: #690;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-s-inner .cm-operator {
|
||||||
|
color: #cda869;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-comment {
|
||||||
|
color: #777;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-string {
|
||||||
|
color: #07a;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-string-2 {
|
||||||
|
color: #bd6b18;
|
||||||
|
} /*?*/
|
||||||
|
.cm-s-inner .cm-meta {
|
||||||
|
color: #000;
|
||||||
|
} /*?*/
|
||||||
|
.cm-s-inner .cm-builtin {
|
||||||
|
color: #9b7536;
|
||||||
|
} /*?*/
|
||||||
|
.cm-s-inner .cm-tag {
|
||||||
|
color: #997643;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-attribute {
|
||||||
|
color: #d6bb6d;
|
||||||
|
} /*?*/
|
||||||
|
.cm-s-inner .cm-header {
|
||||||
|
color: #ff6400;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-hr {
|
||||||
|
color: #aeaeae;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-link {
|
||||||
|
color: #ad9361;
|
||||||
|
font-style: italic;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.cm-s-inner .cm-error {
|
||||||
|
border-bottom: 1px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*div.cm-s-inner .CodeMirror-activeline-background { background: rgba(168, 200, 218, 0.56); }*/
|
||||||
|
div.cm-s-inner span.CodeMirror-matchingbracket {
|
||||||
|
outline: 1px solid grey;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.cm-s-inner .CodeMirror-activeline-background { background: #f8f8f8; }*/
|
||||||
|
.cm-s-inner .CodeMirror-matchingbracket {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**apply to code fences with plan text**/
|
||||||
|
.CodeMirror-lines {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-tooltip {
|
||||||
|
box-shadow: 0 1px 1px 0 rgba(0, 28, 36, 0.3);
|
||||||
|
border-top: 1px solid #eef2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write [data-rmiz-wrap='visible'],
|
||||||
|
#write [data-rmiz-wrap='hidden'] {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write pre {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write pre code {
|
||||||
|
padding: 6px 10px;
|
||||||
|
background-color: var(--chakra-colors-gray-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-fences,
|
||||||
|
#write code,
|
||||||
|
#write tt {
|
||||||
|
border: 1px solid #e7eaed;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
background-color: var(--chakra-colors-gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-fences {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*mathjax-block*/
|
||||||
|
#write mjx-container {
|
||||||
|
font-size: 26px;
|
||||||
|
font-family: 'Apple Symbols';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*table*/
|
||||||
|
#write table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 1px solid #e9eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write th {
|
||||||
|
/* font-family: Quicksand-bold; */
|
||||||
|
background-color: rgba(251, 235, 239, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
#write tbody tr:nth-of-type(2n + 1) {
|
||||||
|
background-color: rgba(251, 235, 239, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#write td,
|
||||||
|
#write th {
|
||||||
|
position: relative;
|
||||||
|
border: solid 1px #e5e3e3;
|
||||||
|
-webkit-user-modify: read-write;
|
||||||
|
padding: 0.8em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write td .md-focus::after,
|
||||||
|
#write th .md-focus::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
border-bottom: 4px solid rgba(166, 200, 233, 0.98);
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write td .md-focus::after,
|
||||||
|
#write th .md-focus::after {
|
||||||
|
visibility: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write td .md-focus,
|
||||||
|
#write th .md-focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*foot notes*/
|
||||||
|
sup.md-footnote {
|
||||||
|
background-image: linear-gradient(30deg, #fbebef, #dff0f5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reversefootnote {
|
||||||
|
color: #c2b8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.md-meta.md-before {
|
||||||
|
color: #c2b8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.md-meta.md-after {
|
||||||
|
color: #c2b8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-def-name::before {
|
||||||
|
color: #c2b8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-def-name {
|
||||||
|
color: #464b1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-def-name::after {
|
||||||
|
color: #c2b8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*horizon rules*/
|
||||||
|
#write hr {
|
||||||
|
border-top: solid 1px #eee;
|
||||||
|
border-bottom: solid 1px #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*toc*/
|
||||||
|
#write p.md-toc-content {
|
||||||
|
position: relative;
|
||||||
|
/*background-color: #f8f8f8;*/
|
||||||
|
padding-left: 145px;
|
||||||
|
margin-left: -100px;
|
||||||
|
margin-right: -100px;
|
||||||
|
margin-top: 0px;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#write .md-toc-item {
|
||||||
|
font-family: var(--monospace);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
#write .md-toc-item a {
|
||||||
|
color: rgba(12, 12, 12, 0.45);
|
||||||
|
transition: all 200ms ease-in-out 0s;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write .md-toc-item a:hover {
|
||||||
|
color: #aad1e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write .md-toc-h1 .md-toc-inner {
|
||||||
|
color: rgba(12, 12, 12, 0.3);
|
||||||
|
/* font-family: Quicksand-bold; */
|
||||||
|
font-size: 1.6rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h2 .md-toc-inner {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-left: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h3 .md-toc-inner {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-left: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h4 .md-toc-inner {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h5 .md-toc-inner {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 11rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 48em) {
|
||||||
|
.md-toc-h3 .md-toc-inner {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-left: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h4 .md-toc-inner {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h5 .md-toc-inner {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 11rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 60em) {
|
||||||
|
.md-toc-h2 .md-toc-inner {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-left: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h3 .md-toc-inner {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-left: 7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h4 .md-toc-inner {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 9.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h5 .md-toc-inner {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 12.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 16rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 78em) {
|
||||||
|
.md-toc-h2 .md-toc-inner {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-left: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h3 .md-toc-inner {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-left: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h4 .md-toc-inner {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 10.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h5 .md-toc-inner {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 13.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-toc-h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 17rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*inline elements*/
|
||||||
|
.md-link a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #e56769;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-link a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-image > .md-meta {
|
||||||
|
color: #c7c5c5;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write mark {
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(231, 153, 176, 0.68);
|
||||||
|
border-radius: 0.2em;
|
||||||
|
/*border: 1px solid #c5c8ca;*/
|
||||||
|
padding: 0.1em 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*sidebar*/
|
||||||
|
.show-folder-name .file-list-item {
|
||||||
|
padding-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.sidebar-content {
|
||||||
|
color: pink;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.pin-outline .outline-active {
|
||||||
|
color: #e799b0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* for example: */
|
||||||
|
.typora-export * {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
/* add styles here */
|
||||||
|
#write input[type='checkbox']::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#write ul li::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#write ul li li::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#write input[type='checkbox'] {
|
||||||
|
appearance: initial;
|
||||||
|
-moz-appearance: initial;
|
||||||
|
-webkit-appearance: initial;
|
||||||
|
}
|
||||||
|
#write ul li {
|
||||||
|
list-style-type: initial;
|
||||||
|
}
|
||||||
|
#write .md-task-list-item {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
#write .md-task-list-item p {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
20
components/DateFormater.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { parseISO, format } from 'date-fns';
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
dateString: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Date: FC<Props> = ({ dateString }) => {
|
||||||
|
const date = parseISO(dateString);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box as="time" lineHeight="0" dateTime={dateString}>
|
||||||
|
{format(date, 'yyyy/MM/dd')}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Date;
|
20
components/Footer.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Flex, Box, Text } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const Footer: FC = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box h="3px" w="50px" bg="gray.500" rounded="xl" mt="2.5rem" />
|
||||||
|
<Flex as="footer" flexFlow="column" py="1.5rem">
|
||||||
|
<Text color={'gray.600'} fontWeight="bold" mb="0.5rem">
|
||||||
|
©{new Date().getFullYear()} 小肥羊
|
||||||
|
</Text>
|
||||||
|
<Text color={'gray.400'} fontSize="small">
|
||||||
|
Powered by Next.js ❤️
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
148
components/NavBar.tsx
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { FC, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Image,
|
||||||
|
Text,
|
||||||
|
Heading,
|
||||||
|
Flex,
|
||||||
|
Icon,
|
||||||
|
useDisclosure,
|
||||||
|
Collapse,
|
||||||
|
useMediaQuery,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import UseAnimations from 'react-useanimations';
|
||||||
|
import menu2 from 'react-useanimations/lib/menu2';
|
||||||
|
import { FiHome, FiArchive, FiTag, FiUser } from 'react-icons/fi';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
const menu = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: '首页',
|
||||||
|
path: '/',
|
||||||
|
icon: FiHome,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '归档',
|
||||||
|
path: '/archive',
|
||||||
|
icon: FiArchive,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '标签',
|
||||||
|
path: '/tags',
|
||||||
|
icon: FiTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '关于',
|
||||||
|
path: '/about',
|
||||||
|
icon: FiUser,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const NavBar: FC = () => {
|
||||||
|
const [isLargerThan768] = useMediaQuery('(min-width: 768px)');
|
||||||
|
const { isOpen, onToggle } = useDisclosure();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isLargerThan768 ? onToggle() : void 0;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isLargerThan768]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
color={'gray.700'}
|
||||||
|
w={[null, null, '120px']}
|
||||||
|
p={['3rem 1rem 1rem 1rem', null, 'unset']}
|
||||||
|
position={'relative'}
|
||||||
|
flex={[1, 1, 'unset']}
|
||||||
|
>
|
||||||
|
<Box position={'relative'} boxSize="120px">
|
||||||
|
{/* avatar */}
|
||||||
|
<Image
|
||||||
|
borderRadius="full"
|
||||||
|
src="/images/img/avatar.webp"
|
||||||
|
boxSize="120px"
|
||||||
|
boxShadow={'card'}
|
||||||
|
objectFit={'cover'}
|
||||||
|
alt="Segun Adebayo"
|
||||||
|
fallbackSrc="https://via.placeholder.com/150"
|
||||||
|
/>
|
||||||
|
{/* emoji on avatar */}
|
||||||
|
<Flex
|
||||||
|
boxShadow={'card'}
|
||||||
|
position={'absolute'}
|
||||||
|
bottom={0}
|
||||||
|
right={0}
|
||||||
|
rounded={'full'}
|
||||||
|
bg="white"
|
||||||
|
h="40px"
|
||||||
|
w="40px"
|
||||||
|
justify="center"
|
||||||
|
alignItems={'center'}
|
||||||
|
>
|
||||||
|
❤️
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Heading size={'lg'} mt="0.8rem" mb="0.5rem">
|
||||||
|
肥羊
|
||||||
|
</Heading>
|
||||||
|
<Text color={'gray.400'}>This is bio.</Text>
|
||||||
|
|
||||||
|
{/* Menu */}
|
||||||
|
<Box mx={['-2rem', '-4rem', 'unset']}>
|
||||||
|
<Collapse in={isOpen} animateOpacity>
|
||||||
|
<Flex
|
||||||
|
as="nav"
|
||||||
|
mt={['1.5rem', null, '2.5rem']}
|
||||||
|
flexFlow={['column']}
|
||||||
|
py={['1rem', null, 'unset']}
|
||||||
|
px={['2rem', '4rem', 'unset']}
|
||||||
|
bg={['white', null, 'unset']}
|
||||||
|
shadow={['card', null, 'unset']}
|
||||||
|
>
|
||||||
|
{menu.map((item) => {
|
||||||
|
return (
|
||||||
|
<Link passHref href={item.path} key={item.id}>
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent={['unset', null, 'space-between']}
|
||||||
|
fontSize={['18', null, '22']}
|
||||||
|
color="gray.600"
|
||||||
|
my={['0.5rem', null, '0.5rem']}
|
||||||
|
cursor="pointer"
|
||||||
|
>
|
||||||
|
<Icon as={item.icon} mr={['2.5rem', null, 'unset']} />
|
||||||
|
<Text>{item.name}</Text>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Collapse>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
display={[null, null, 'none']}
|
||||||
|
position={'absolute'}
|
||||||
|
top={'1.5rem'}
|
||||||
|
right={'1rem'}
|
||||||
|
onClick={onToggle}
|
||||||
|
>
|
||||||
|
<UseAnimations
|
||||||
|
reverse={isOpen}
|
||||||
|
size={40}
|
||||||
|
animation={menu2}
|
||||||
|
speed={1.5}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavBar;
|
44
components/Paging.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { Button, Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
allPages: number;
|
||||||
|
num: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paging: FC<Props> = ({ allPages, num }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const startIndex = Number(num);
|
||||||
|
|
||||||
|
const goPrev = () => {
|
||||||
|
startIndex == 2 ? router.push('/') : router.push(`/page/${startIndex - 1}`);
|
||||||
|
};
|
||||||
|
const goNext = () => {
|
||||||
|
router.push(`/page/${startIndex + 1}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex>
|
||||||
|
{startIndex > 1 ? (
|
||||||
|
<Button onClick={goPrev} mr={'auto'} size="lg">
|
||||||
|
上一页
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
void 0
|
||||||
|
)}
|
||||||
|
|
||||||
|
{startIndex != allPages ? (
|
||||||
|
<Button onClick={goNext} ml={'auto'} size="lg">
|
||||||
|
下一页
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
void 0
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default paging;
|
63
components/PostCard.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Date from './DateFormater';
|
||||||
|
import { Box, Flex, Heading, Text } from '@chakra-ui/react';
|
||||||
|
import { AllPostsData } from '../lib/posts';
|
||||||
|
import { Icon, Image } from '@chakra-ui/react';
|
||||||
|
import { FiCalendar } from 'react-icons/fi';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
post: AllPostsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PostCard: FC<Props> = ({ post }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
as="article"
|
||||||
|
maxW={'48rem'}
|
||||||
|
borderRadius={'10px'}
|
||||||
|
bg={'white'}
|
||||||
|
overflow="hidden"
|
||||||
|
boxShadow="card"
|
||||||
|
mb={'2.5rem'}
|
||||||
|
>
|
||||||
|
<Link href={`/posts/${post.url}`}>
|
||||||
|
<a>
|
||||||
|
<Image
|
||||||
|
src={post.index_img}
|
||||||
|
maxH="18rem"
|
||||||
|
w="100%"
|
||||||
|
fallback={<></>}
|
||||||
|
fit="cover"
|
||||||
|
alt="Post image"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Box p={'28px'} key={post.url}>
|
||||||
|
<Link href={`/posts/${post.url}`}>
|
||||||
|
<a>
|
||||||
|
<Heading mb="0.6rem" fontSize="22">
|
||||||
|
{post.title}
|
||||||
|
</Heading>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Text mb="0.6rem" color="gray.600">
|
||||||
|
{post.desc}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Flex alignItems="center" fontSize="13" color="gray.500">
|
||||||
|
<Flex alignItems="center" mr="1rem">
|
||||||
|
<Icon as={FiCalendar} mr="0.5rem" />
|
||||||
|
<Date dateString={post.date} />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PostCard;
|
60
layouts/HomeLayout.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { FC, useRef } from 'react';
|
||||||
|
import NavBar from '../components/NavBar';
|
||||||
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
import Footer from '../components/Footer';
|
||||||
|
import { BackTop } from 'antd';
|
||||||
|
import UseAnimations from 'react-useanimations';
|
||||||
|
import arrowUp from 'react-useanimations/lib/arrowUp';
|
||||||
|
|
||||||
|
const HomeLayout: FC = ({ children }) => {
|
||||||
|
const wrapperRef = useRef(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
justify={'center'}
|
||||||
|
overflow="auto"
|
||||||
|
h={['unset', 'unset', '100vh']}
|
||||||
|
px={['1rem', '1rem', '3rem']}
|
||||||
|
flexFlow={['column', 'column', 'row']}
|
||||||
|
ref={wrapperRef}
|
||||||
|
>
|
||||||
|
{/* Here is NavBar on the left */}
|
||||||
|
<Flex
|
||||||
|
as="header"
|
||||||
|
position={'sticky'}
|
||||||
|
top={['0', '0', '3rem']}
|
||||||
|
w={[null, null, '120px', '11rem']}
|
||||||
|
mr={[null, null, '3rem']}
|
||||||
|
mt={[null, null, '3rem']}
|
||||||
|
>
|
||||||
|
<NavBar />
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<Box mt={['1rem', null, '3rem']} as="main">
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<BackTop
|
||||||
|
target={() => (wrapperRef.current ? wrapperRef.current : document.body)}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
bg="white"
|
||||||
|
w="40px"
|
||||||
|
h="40px"
|
||||||
|
justify="center"
|
||||||
|
alignItems="center"
|
||||||
|
rounded="full"
|
||||||
|
shadow="card"
|
||||||
|
>
|
||||||
|
<UseAnimations size={32} animation={arrowUp} />
|
||||||
|
</Flex>
|
||||||
|
</BackTop>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomeLayout;
|
176
lib/posts.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import matter from 'gray-matter';
|
||||||
|
import markdownToTxt from 'markdown-to-txt';
|
||||||
|
|
||||||
|
const postsDirectory = path.join(process.cwd(), 'public/posts');
|
||||||
|
|
||||||
|
export interface MyMatters {
|
||||||
|
title: string;
|
||||||
|
date: string;
|
||||||
|
tags: string;
|
||||||
|
categories: string;
|
||||||
|
url: string;
|
||||||
|
index_img: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AllPostsData extends MyMatters {
|
||||||
|
id: string;
|
||||||
|
desc: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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$/, '');
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Process markdown to plain text
|
||||||
|
const contentText = markdownToTxt(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),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort posts by date
|
||||||
|
return allPostsData.sort(({ date: a }, { date: b }) => {
|
||||||
|
if (a < b) {
|
||||||
|
return 1;
|
||||||
|
} else if (a > b) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert Date to locale string.
|
||||||
|
// return allPostsData.map((post) => {
|
||||||
|
// if (typeof post.date == 'object') post.date = post.date.toISOString();
|
||||||
|
// return post;
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paging, get all posts length
|
||||||
|
export function getAllPostNum() {
|
||||||
|
// Get file names under /posts
|
||||||
|
const fileNames = fs.readdirSync(postsDirectory);
|
||||||
|
|
||||||
|
// Returns an array that looks like this:
|
||||||
|
// [
|
||||||
|
// {
|
||||||
|
// params: {
|
||||||
|
// num: '2' // num of page
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// params: {
|
||||||
|
// num: '3'
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
let i = 1;
|
||||||
|
return fileNames.map(() => {
|
||||||
|
i++;
|
||||||
|
return {
|
||||||
|
params: {
|
||||||
|
num: i.toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPagingData(allPostsData: AllPostsData[], start?: string) {
|
||||||
|
const totalNum = allPostsData.length;
|
||||||
|
const pagingSize = 10;
|
||||||
|
const allPages = Math.ceil(totalNum / pagingSize);
|
||||||
|
|
||||||
|
const startIndex = start ? (Number(start) - 1) * pagingSize : 0;
|
||||||
|
|
||||||
|
const postDatas = allPostsData.slice(startIndex, startIndex + 10);
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalNum,
|
||||||
|
pagingSize,
|
||||||
|
allPages,
|
||||||
|
postDatas,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllPostSlugs() {
|
||||||
|
const fileNames = fs.readdirSync(postsDirectory);
|
||||||
|
|
||||||
|
// Returns an array that looks like this:
|
||||||
|
// [
|
||||||
|
// {
|
||||||
|
// params: {
|
||||||
|
// id: 'ssg-ssr'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// params: {
|
||||||
|
// id: 'pre-rendering'
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
return fileNames.map((fileName) => {
|
||||||
|
const allPostsData = getSortedPostsData();
|
||||||
|
const slug = allPostsData.find(
|
||||||
|
(item) => item.id === fileName.replace(/\.md$/, '')
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
params: {
|
||||||
|
slug: slug?.url,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MyPost extends AllPostsData {
|
||||||
|
contentHtml: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPostData(slug: string) {
|
||||||
|
const allPostsData = getSortedPostsData();
|
||||||
|
const post = allPostsData.find((item) => item.url === slug);
|
||||||
|
|
||||||
|
const fullPath = path.join(postsDirectory, `${post?.id}.md`);
|
||||||
|
const fileContents = fs.readFileSync(fullPath, 'utf8');
|
||||||
|
|
||||||
|
// Use gray-matter to parse the post metadata section
|
||||||
|
const matterResult = matter(fileContents);
|
||||||
|
|
||||||
|
// Process markdown to html
|
||||||
|
// const processedContent = await remark()
|
||||||
|
// .use(html({ sanitize: false }))
|
||||||
|
// .process(matterResult.content);
|
||||||
|
// const contentHtml = processedContent.toString();
|
||||||
|
|
||||||
|
// Combine the data with the id
|
||||||
|
return {
|
||||||
|
id: slug,
|
||||||
|
// contentHtml,
|
||||||
|
content: matterResult.content,
|
||||||
|
// Convert Date to locale string.
|
||||||
|
...({
|
||||||
|
...matterResult.data,
|
||||||
|
date: matterResult.data.date.toISOString(),
|
||||||
|
} as MyMatters),
|
||||||
|
};
|
||||||
|
}
|
6
next-env.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/types/global" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
4
next.config.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
module.exports = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
}
|
48
package.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "blog",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"build:export": "next build && next export",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/react": "^1.6.10",
|
||||||
|
"@emotion/react": "^11",
|
||||||
|
"@emotion/styled": "^11",
|
||||||
|
"antd": "^4.16.13",
|
||||||
|
"autoprefixer": "^10.3.7",
|
||||||
|
"date-fns": "^2.25.0",
|
||||||
|
"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",
|
||||||
|
"react-dom": "17.0.2",
|
||||||
|
"react-icons": "^4.3.1",
|
||||||
|
"react-medium-image-zoom": "^4.3.5",
|
||||||
|
"react-useanimations": "^2.0.6",
|
||||||
|
"rehype-highlight": "^5.0.0",
|
||||||
|
"rehype-raw": "^6.1.0",
|
||||||
|
"rehype-react": "^7.0.3",
|
||||||
|
"rehype-slug": "^5.0.0",
|
||||||
|
"remark": "^14.0.1",
|
||||||
|
"remark-html": "^15.0.0",
|
||||||
|
"remark-parse": "^10.0.0",
|
||||||
|
"remark-rehype": "^10.0.1",
|
||||||
|
"remark-toc": "^8.0.1",
|
||||||
|
"unified": "^10.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "17.0.31",
|
||||||
|
"eslint": "7.32.0",
|
||||||
|
"eslint-config-next": "11.1.2",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
"typescript": "4.4.4"
|
||||||
|
}
|
||||||
|
}
|
41
pages/_app.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import '../styles/globals.css';
|
||||||
|
import type { ReactElement, ReactNode } from 'react';
|
||||||
|
import type { NextPage } from 'next';
|
||||||
|
import type { AppProps } from 'next/app';
|
||||||
|
import { ChakraProvider, extendTheme } from '@chakra-ui/react';
|
||||||
|
import 'antd/dist/antd.css';
|
||||||
|
import '../assets/css/rua.css';
|
||||||
|
|
||||||
|
const theme = extendTheme({
|
||||||
|
colors: {
|
||||||
|
home: {
|
||||||
|
bg: '#f5f5fa',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shadows: {
|
||||||
|
card: '0px 4px 8px rgba(0, 0, 0, 0.04),0px 0px 2px rgba(0, 0, 0, 0.06),0px 0px 1px rgba(0, 0, 0, 0.04)',
|
||||||
|
},
|
||||||
|
fonts: {
|
||||||
|
body: "-apple-system,BlinkMacSystemFont,'Helvetica Neue',Helvetica,Segoe UI,Arial,Roboto,'PingFang SC',miui,'Hiragino Sans GB','Microsoft Yahei',sans-serif",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type NextPageWithLayout = NextPage & {
|
||||||
|
getLayout?: (page: ReactElement) => ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AppPropsWithLayout = AppProps & {
|
||||||
|
Component: NextPageWithLayout;
|
||||||
|
};
|
||||||
|
|
||||||
|
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||||
|
// Use the layout defined at the page level, if available
|
||||||
|
const getLayout = Component.getLayout ?? ((page) => page);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraProvider theme={theme}>
|
||||||
|
{getLayout(<Component {...pageProps} />)}
|
||||||
|
</ChakraProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default MyApp;
|
13
pages/api/hello.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<Data>
|
||||||
|
) {
|
||||||
|
res.status(200).json({ name: 'John Doe' })
|
||||||
|
}
|
42
pages/index.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import type { InferGetStaticPropsType } from 'next';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import Paging from '../components/Paging';
|
||||||
|
import type { ReactElement } from 'react';
|
||||||
|
import { getPagingData, getSortedPostsData } from '../lib/posts';
|
||||||
|
import HomeLayout from '../layouts/HomeLayout';
|
||||||
|
import PostCard from '../components/PostCard';
|
||||||
|
|
||||||
|
export const getStaticProps = async () => {
|
||||||
|
const allPostsData = getSortedPostsData();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...getPagingData(allPostsData),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Home = ({
|
||||||
|
allPages,
|
||||||
|
postDatas,
|
||||||
|
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>RUA - Home</title>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
{postDatas.map((post) => (
|
||||||
|
<PostCard key={post.id} post={post} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Paging allPages={allPages} num={'1'} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Home.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <HomeLayout>{page}</HomeLayout>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
57
pages/page/[num].tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import type { GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import {
|
||||||
|
AllPostsData,
|
||||||
|
getAllPostNum,
|
||||||
|
getPagingData,
|
||||||
|
getSortedPostsData,
|
||||||
|
} from '../../lib/posts';
|
||||||
|
import Paging from '../../components/Paging';
|
||||||
|
import PostCard from '../../components/PostCard';
|
||||||
|
import HomeLayout from '../../layouts/HomeLayout';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const paths = getAllPostNum();
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
fallback: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||||
|
const allPostsData = getSortedPostsData();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
num: params?.num?.toString(),
|
||||||
|
...getPagingData(allPostsData, params?.num?.toString()),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = ({
|
||||||
|
num,
|
||||||
|
allPages,
|
||||||
|
postDatas,
|
||||||
|
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>RUA - Home</title>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
{postDatas.map((post: AllPostsData) => (
|
||||||
|
<PostCard key={post.id} post={post} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Paging allPages={allPages} num={num} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Page.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <HomeLayout>{page}</HomeLayout>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
179
pages/posts/[slug].tsx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import { getAllPostSlugs, getPostData } from '../../lib/posts';
|
||||||
|
import { GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import Date from '../../components/DateFormater';
|
||||||
|
import remarkToc from 'remark-toc';
|
||||||
|
import rehypeSlug from 'rehype-slug';
|
||||||
|
import rehypeReact from 'rehype-react';
|
||||||
|
import remarkRehype from 'remark-rehype';
|
||||||
|
import remarkParse from 'remark-parse';
|
||||||
|
import rehypeHighlight from 'rehype-highlight';
|
||||||
|
import { unified } from 'unified';
|
||||||
|
import { createElement, Fragment } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Image,
|
||||||
|
Heading,
|
||||||
|
Flex,
|
||||||
|
Icon,
|
||||||
|
Link,
|
||||||
|
Button,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import 'highlight.js/styles/github.css';
|
||||||
|
import xml from 'highlight.js/lib/languages/xml';
|
||||||
|
import bash from 'highlight.js/lib/languages/bash';
|
||||||
|
import rehypeRaw from 'rehype-raw';
|
||||||
|
import Zoom from 'react-medium-image-zoom';
|
||||||
|
import 'react-medium-image-zoom/dist/styles.css';
|
||||||
|
import { FiCalendar } from 'react-icons/fi';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const paths = getAllPostSlugs();
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
fallback: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||||
|
const postData = await getPostData(params?.slug?.toString() ?? '');
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
postData,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Post = ({ postData }: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const processedContent = unified()
|
||||||
|
.use(remarkParse)
|
||||||
|
.use(remarkToc, {
|
||||||
|
maxDepth: 3,
|
||||||
|
})
|
||||||
|
.use(remarkRehype, { allowDangerousHtml: true })
|
||||||
|
.use(rehypeRaw)
|
||||||
|
.use(rehypeHighlight, {
|
||||||
|
languages: { vue: xml, bash },
|
||||||
|
aliases: { bash: ['npm'] },
|
||||||
|
ignoreMissing: true,
|
||||||
|
})
|
||||||
|
.use(rehypeSlug)
|
||||||
|
.use(rehypeReact, {
|
||||||
|
createElement,
|
||||||
|
components: {
|
||||||
|
img: (props: any) => {
|
||||||
|
return (
|
||||||
|
<Zoom wrapElement="a">
|
||||||
|
<Image borderRadius="10px" src={props.src} alt="" />
|
||||||
|
</Zoom>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
a: (props: any) => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Link display="inline-flex" alignItems="center" href={props.href}>
|
||||||
|
{props.children}
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Fragment,
|
||||||
|
});
|
||||||
|
|
||||||
|
const postContent = processedContent.processSync(
|
||||||
|
`\n## Table of contents\n${postData.content}`
|
||||||
|
).result;
|
||||||
|
|
||||||
|
// Process the table of content
|
||||||
|
const tocHead = (postContent.props as any).children.shift();
|
||||||
|
(postContent.props as any).children.shift(); // '\n'
|
||||||
|
let toc;
|
||||||
|
// Table of content is ul element
|
||||||
|
if ((postContent.props as any).children[0].type === 'ul') {
|
||||||
|
toc = (postContent.props as any).children.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>RUA - {postData.title}</title>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
{/* Child will not be 100% height, so need alignItems="flex-start" */}
|
||||||
|
<Flex
|
||||||
|
h={['unset', 'unset', '100vh']}
|
||||||
|
justifyContent="center"
|
||||||
|
overflow="auto"
|
||||||
|
alignItems="flex-start"
|
||||||
|
flexFlow={['column', 'column', 'row']}
|
||||||
|
px="0.5rem"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
mt={['1rem', '1rem', '3rem']}
|
||||||
|
position={['unset', 'unset', 'sticky']}
|
||||||
|
top="3rem"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
>
|
||||||
|
BACK
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
w={['full', 'full', '55rem']}
|
||||||
|
borderRadius="10px"
|
||||||
|
mt={['1rem', '1rem', '3rem']}
|
||||||
|
shadow="lg"
|
||||||
|
bg="white"
|
||||||
|
mx={['unset', 'unset', '1.5rem']}
|
||||||
|
overflow="hidden"
|
||||||
|
flex="1"
|
||||||
|
maxW="55rem"
|
||||||
|
>
|
||||||
|
{postData.index_img && (
|
||||||
|
<Image
|
||||||
|
src={postData.index_img}
|
||||||
|
w="100%"
|
||||||
|
fallback={<></>}
|
||||||
|
fit="cover"
|
||||||
|
alt="Head image"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Box as="article" p={['1rem', '1rem', '2rem']}>
|
||||||
|
<Box as="header">
|
||||||
|
<Heading as="h1" mb="1rem">
|
||||||
|
{postData.title}
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
<Flex alignItems="center" color="gray.600">
|
||||||
|
<Icon as={FiCalendar} mr="0.5rem" />
|
||||||
|
<Date dateString={postData.date} />
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box as="section" id="write" mt="2rem">
|
||||||
|
{postContent}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{toc && (
|
||||||
|
<Box
|
||||||
|
display={['none', 'none', 'none', 'block']}
|
||||||
|
position="sticky"
|
||||||
|
top="3rem"
|
||||||
|
mt="3rem"
|
||||||
|
>
|
||||||
|
{tocHead}
|
||||||
|
{toc}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Post;
|
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
public/images/ASCII在线视频流/ascii_cat.gif
Normal file
After Width: | Height: | Size: 871 KiB |
BIN
public/images/ASCII在线视频流/logo.webp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
public/images/ASCII在线视频流/图像-1.webp
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
public/images/ASCII在线视频流/图像-2.webp
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
public/images/AliOssForTypecho/logo.webp
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
public/images/BOM/2020-08-15-10-19-42.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
public/images/Can't install gifsicle/index.webp
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/images/Can't install gifsicle/批注 2020-08-04 135700.webp
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
public/images/Docker-构建属于自己的镜像/logo.webp
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
public/images/Docker全面容器化/image-20191221185631177.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/Docker全面容器化/logo.webp
Normal file
After Width: | Height: | Size: 936 B |
BIN
public/images/Gitlab尝鲜/411390967.webp
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
public/images/Gitlab尝鲜/52152339.webp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
public/images/Gitlab尝鲜/99634888.webp
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
public/images/Header实践-得拿下这个A/header-security.webp
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
public/images/Header实践-得拿下这个A/image-20191218171928050.webp
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
public/images/Header实践-得拿下这个A/image-20191218172218399.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/Hexo-and-Github/create.webp
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
public/images/Hexo-and-Github/ggg.webp
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
public/images/Hexo-and-Github/index.webp
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
public/images/Hexo-and-Github/profile.webp
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
public/images/Hexo-and-Github/repository.webp
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
public/images/Hexo-and-Github/web.webp
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
public/images/JavaScript-可迭代对象与for-of/2020-10-27-15-01-39.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/JavaScript-可迭代对象与for-of/2020-10-29-10-17-49.webp
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
public/images/JavaScript-可迭代对象与for-of/logo.webp
Normal file
After Width: | Height: | Size: 8.3 KiB |
875
public/images/JavaScript中的事件/concentirc-circles.ai
Normal file
BIN
public/images/JavaScript中的事件/concentirc-circles.png
Normal file
After Width: | Height: | Size: 31 KiB |
1517
public/images/JavaScript中的事件/event-bubbling.ai
Normal file
1
public/images/JavaScript中的事件/event-bubbling.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="252.18" height="369" viewBox="0 0 252.18 369"><defs><style>.cls-1{fill:#666;}.cls-2,.cls-4{fill:#fff;}.cls-3{font-size:15px;font-family:Consolas;}.cls-4{stroke:#00a99d;}.cls-4,.cls-5{stroke-miterlimit:10;stroke-width:2px;}.cls-5{fill:none;stroke:#000;}</style></defs><rect class="cls-1" x="72.52" y="48" width="2" height="60"/><rect class="cls-2" x="1" y="1" width="148" height="48" rx="9"/><path class="cls-1" d="M901.48,182a8,8,0,0,1,8,8v30a8,8,0,0,1-8,8h-130a8,8,0,0,1-8-8V190a8,8,0,0,1,8-8h130m0-2h-130a10,10,0,0,0-10,10v30a10,10,0,0,0,10,10h130a10,10,0,0,0,10-10V190a10,10,0,0,0-10-10Z" transform="translate(-761.48 -180)"/><text class="cls-3" transform="translate(42.01 28.88)">Document</text><rect class="cls-2" x="1" y="107" width="148" height="48" rx="9"/><path class="cls-1" d="M901.48,288a8,8,0,0,1,8,8v30a8,8,0,0,1-8,8h-130a8,8,0,0,1-8-8V296a8,8,0,0,1,8-8h130m0-2h-130a10,10,0,0,0-10,10v30a10,10,0,0,0,10,10h130a10,10,0,0,0,10-10V296a10,10,0,0,0-10-10Z" transform="translate(-761.48 -180)"/><text class="cls-3" transform="translate(25.52 134.88)">Element <tspan class="cls-1" x="65.98" y="0">html</tspan></text><rect class="cls-2" x="1" y="213" width="148" height="48" rx="9"/><path class="cls-1" d="M901.48,394a8,8,0,0,1,8,8v30a8,8,0,0,1-8,8h-130a8,8,0,0,1-8-8V402a8,8,0,0,1,8-8h130m0-2h-130a10,10,0,0,0-10,10v30a10,10,0,0,0,10,10h130a10,10,0,0,0,10-10V402a10,10,0,0,0-10-10Z" transform="translate(-761.48 -180)"/><text class="cls-3" transform="translate(25.52 239.88)">Element <tspan class="cls-1" x="65.98" y="0">body</tspan></text><rect class="cls-2" x="1" y="320" width="148" height="48" rx="9"/><path class="cls-1" d="M901.48,501a8,8,0,0,1,8,8v30a8,8,0,0,1-8,8h-130a8,8,0,0,1-8-8V509a8,8,0,0,1,8-8h130m0-2h-130a10,10,0,0,0-10,10v30a10,10,0,0,0,10,10h130a10,10,0,0,0,10-10V509a10,10,0,0,0-10-10Z" transform="translate(-761.48 -180)"/><text class="cls-3" transform="translate(29.64 347.88)">Element <tspan class="cls-1" x="65.98" y="0">div</tspan></text><rect class="cls-1" x="72.52" y="154" width="2" height="60"/><rect class="cls-1" x="72.52" y="261" width="2" height="60"/><circle class="cls-4" cx="187.52" cy="346" r="20"/><text class="cls-3" transform="translate(183.39 349.88)">1</text><circle class="cls-4" cx="187.52" cy="237" r="20"/><text class="cls-3" transform="translate(183.39 240.88)">2</text><circle class="cls-4" cx="187.52" cy="133" r="20"/><text class="cls-3" transform="translate(183.39 136.88)">3</text><circle class="cls-4" cx="187.52" cy="25" r="20"/><text class="cls-3" transform="translate(183.39 28.88)">4</text><path class="cls-5" d="M973.67,532s78.85-55.35,12.31-98.08" transform="translate(-761.48 -180)"/><polygon points="212.18 247 233.49 249.85 225.35 254.55 225.4 263.95 212.18 247"/><path class="cls-5" d="M973.67,424s78.85-55.35,12.31-98.08" transform="translate(-761.48 -180)"/><polygon points="212.18 139 233.49 141.85 225.35 146.55 225.4 155.95 212.18 139"/><path class="cls-5" d="M973.67,313s78.85-55.35,12.31-98.08" transform="translate(-761.48 -180)"/><polygon points="212.18 28 233.49 30.85 225.35 35.55 225.4 44.95 212.18 28"/></svg>
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/images/JavaScript实践-乘法表/99-1581995512696.webp
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
public/images/JavaScript实践-乘法表/logo.webp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/images/JavaScript的作用域与链/index.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/images/JavaScript的作用域与链/scope.psd
Normal file
BIN
public/images/JavaScript的作用域与链/scope.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/images/JavaScript的函数/index.webp
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/images/JavaScript笔记-引用类型/javascript.webp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/images/JavaScript装饰器模式/logo.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/images/JavaScript零碎笔记/image-20200331133634029.png
Normal file
After Width: | Height: | Size: 257 KiB |
BIN
public/images/JavaScript零碎笔记/image-20200331134656244.png
Normal file
After Width: | Height: | Size: 376 KiB |
BIN
public/images/JavaScript零碎笔记/image-20200331135348855.png
Normal file
After Width: | Height: | Size: 364 KiB |
BIN
public/images/JavaScript零碎笔记/image-20200331195638452.png
Normal file
After Width: | Height: | Size: 491 KiB |
BIN
public/images/JavaScript零碎笔记/image-20200401112039274.png
Normal file
After Width: | Height: | Size: 569 KiB |
BIN
public/images/JavaScript零碎笔记/image-20200427212659150.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/images/JavaScript零碎笔记/image-20201201114428274.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/images/JavaScript零碎笔记/image-20201201114430208.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/images/JavaScript零碎笔记/image-20201201115114508.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
public/images/JavaScript面向对象的程序设计/image-20200726171729248.webp
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
public/images/JavaScript面向对象的程序设计/logo.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/images/Minecraft-bedrock服务端/1339714059.webp
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
public/images/Minecraft-bedrock服务端/2032314932.webp
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
public/images/Minecraft-bedrock服务端/217940907.webp
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
public/images/Minecraft-bedrock服务端/3200057918.webp
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
public/images/Minecraft-bedrock服务端/4100402335.webp
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/images/Node.js之旅/2020-09-03-14-54-53.webp
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/images/Node.js之旅/2020-09-03-16-16-04.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/Nodejs多进程/logo.webp
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
public/images/QinQ基础操作/ethernet-QinQ-format2.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/QinQ基础操作/qinq.webp
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
public/images/QinQ基础操作/vlan20.webp
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
public/images/QinQ基础操作/基本qinq.webp
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
public/images/QinQ基础操作/外层type.webp
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
public/images/QinQ基础操作/报文-1582421531943.webp
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
public/images/QinQ基础操作/报文.webp
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
public/images/QinQ基础操作/报文2.webp
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
public/images/QinQ基础操作/报文3.webp
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
public/images/QinQ基础操作/灵活拓扑.webp
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/images/Resilio-Sync多平台实时同步/home_centos.webp
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/images/Resilio-Sync多平台实时同步/res.webp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/images/Resilio-Sync多平台实时同步/rs安装.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/Resilio-Sync多平台实时同步/入门sync.webp
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
public/images/Resilio-Sync多平台实时同步/其他选项.webp
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
public/images/Resilio-Sync多平台实时同步/开始同步.webp
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
public/images/Resilio-Sync多平台实时同步/打开.webp
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
public/images/Resilio-Sync多平台实时同步/输入密钥.webp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/images/Teamspeak-Server/TS3-1582422801541.webp
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
public/images/Teamspeak-Server/TS3.webp
Normal file
After Width: | Height: | Size: 39 KiB |