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 |