Create blog with nextjs

This commit is contained in:
DefectingCat
2021-11-16 21:00:46 +08:00
commit fe57cec9d5
302 changed files with 31902 additions and 0 deletions

6
.dockerignore Normal file
View File

@ -0,0 +1,6 @@
git
dist
build
db.json
public
node_modules

6
.eslintrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": ["next", "prettier"],
"rules": {
"react-hooks/rules-of-hooks": "off"
}
}

34
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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;
}
}

View 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
View 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">
&copy;{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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
}

48
package.json Normal file
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Some files were not shown because too many files have changed in this diff Show More