Browse Source

refactor: using next.js instead umi.js(#master)

master
OhYee 3 years ago
parent
commit
00e457692e
Signed by: OhYee
GPG Key ID: 5A9E1F63ED274FBB
  1. 16
      .editorconfig
  2. 5
      .env
  3. 3
      .eslintrc
  4. 4
      .gitignore
  5. 7
      .prettierignore
  6. 11
      .prettierrc
  7. 30
      .umirc.ts
  8. 21
      LICENSE
  9. 10
      README.md
  10. 3
      components/comment.tsx
  11. 0
      components/container.tsx
  12. 108
      components/layout.less
  13. 275
      components/layout.tsx
  14. 14
      components/post_card.tsx
  15. 0
      components/post_list.tsx
  16. 0
      components/tag.less
  17. 18
      components/tag.tsx
  18. 1
      components/tag_search.tsx
  19. 0
      components/visiable.tsx
  20. 0
      mock/.gitkeep
  21. 20
      mock/avatar.ts
  22. 61
      mock/comment.ts
  23. 104
      mock/friends.ts
  24. 81
      mock/index.ts
  25. 37
      mock/menu.ts
  26. 59
      mock/post.ts
  27. 2
      next-env.d.ts
  28. 75
      next.config.js
  29. 82
      package.json
  30. 49
      pages/_app.tsx
  31. 34
      pages/about.tsx
  32. 13
      pages/admin/index.tsx
  33. 78
      pages/admin/post.tsx
  34. 36
      pages/admin/posts.tsx
  35. 79
      pages/archives.tsx
  36. 36
      pages/comment.tsx
  37. 0
      pages/friends.less
  38. 24
      pages/friends.tsx
  39. 19
      pages/index.tsx
  40. 46
      pages/post/[url].tsx
  41. 0
      pages/post/post.less
  42. 83
      pages/tag/[tag].tsx
  43. 0
      pages/tags.less
  44. 28
      pages/tags.tsx
  45. 94
      server.js
  46. 9
      src/app.ts
  47. 9
      src/assets/logo.svg
  48. BIN
      src/assets/yay.jpg
  49. 142
      src/components/menu.tsx
  50. 56
      src/global.less
  51. 14
      src/index.css
  52. 16
      src/layouts/__tests__/index.test.tsx
  53. 6
      src/layouts/footer.less
  54. 43
      src/layouts/footer.tsx
  55. 27
      src/layouts/header.tsx
  56. 23
      src/layouts/index.less
  57. 104
      src/layouts/index.tsx
  58. 20
      src/layouts/sider.less
  59. 34
      src/layouts/sider.tsx
  60. 0
      src/models/.gitkeep
  61. 9
      src/models/token.ts
  62. 18
      src/pages/404.tsx
  63. 16
      src/pages/__tests__/index.test.tsx
  64. 34
      src/pages/about.tsx
  65. 85
      src/pages/archives.tsx
  66. 41
      src/pages/comment.tsx
  67. 24
      src/pages/document.ejs
  68. 89
      src/pages/tag/$tag$.tsx
  69. 6
      src/utils/prerender.d.ts
  70. 35
      src/utils/prerender.js
  71. 37
      tsconfig.json
  72. 13
      tslint.yml
  73. 0
      types/typings.d.ts
  74. 17
      utils/api.ts
  75. 0
      utils/debounce.ts
  76. 15
      utils/global.ts
  77. 0
      utils/notification.ts
  78. 16
      utils/parse.ts
  79. 4
      utils/request.ts
  80. 0
      utils/responsive.ts
  81. 0
      utils/sort.ts

16
.editorconfig

@ -1,16 +0,0 @@ @@ -1,16 +0,0 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

5
.env

@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
BROWSER=none
ESLINT=1
REACT_EDITOR=code
PORT=50001
SOCKET_SERVER=none

3
.eslintrc

@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
{
"extends": "eslint-config-umi"
}

4
.gitignore vendored

@ -13,8 +13,6 @@ @@ -13,8 +13,6 @@
# misc
.DS_Store
# umi
.umi
.umi-production
.next
public

7
.prettierignore

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
**/*.md
**/*.svg
**/*.ejs
**/*.html
package.json
.umi
.umi-production

11
.prettierrc

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}

30
.umirc.ts

@ -1,30 +0,0 @@ @@ -1,30 +0,0 @@
import { IConfig } from 'umi-types'; // ref: https://umijs.org/config/
const config: IConfig = {
treeShaking: true,
ssr: true,
hash: process.env.NODE_ENV === 'production',
plugins: [
// ref: https://umijs.org/plugin/umi-plugin-react.html
[
'umi-plugin-react',
{
antd: true,
dva: true,
dynamicImport: false,
title: 'blotter_page',
dll: true,
routes: {
exclude: [
/models\//,
/services\//,
/model\.(t|j)sx?$/,
/service\.(t|j)sx?$/,
/components\//,
],
},
},
],
],
};
export default config;

21
LICENSE

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 OhYee
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

10
README.md

@ -1,10 +0,0 @@ @@ -1,10 +0,0 @@
# Blotter page
Web page for [blotter](https://github.com/OhYee/blotter)
## Start
```bash
yarn
yarn start
```

3
src/components/comment.tsx → components/comment.tsx

@ -49,6 +49,9 @@ class CommentPart extends React.Component< @@ -49,6 +49,9 @@ class CommentPart extends React.Component<
comments: [],
loading: true,
};
}
componentDidMount() {
this.initialComment();
}

0
src/components/container.tsx → components/container.tsx

108
components/layout.less

@ -0,0 +1,108 @@ @@ -0,0 +1,108 @@
.main_content {
margin: 20px;
}
.dimmed::before {
content: ' ';
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
background: rgba(0, 0, 0, 0);
animation: dimmed-change 0.5s 1 alternate;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
@keyframes dimmed-change {
100% {
background: rgba(0, 0, 0, 0.5);
}
}
.sider {
.avatar {
margin: 20px 0;
-webkit-transition: 0.4s;
-webkit-transition: -webkit-transform 0.4s ease-out;
transition: transform 0.4s ease-out;
-moz-transition: -moz-transform 0.4s ease-out;
}
.avatar:hover {
transform: rotateZ(360deg);
-webkit-transform: rotateZ(360deg);
-moz-transform: rotateZ(360deg);
}
.divider {
:global(.ant-divider-inner-text) {
padding: 5px 0;
}
}
}
html,
body,
#root {
height: 100%;
}
body {
margin: 0;
}
:global(.shadow) {
-moz-box-shadow: 2px 2px 2px #ccc;
-webkit-box-shadow: 2px 2px 2px #ccc;
box-shadow: 2px 2px 2px 2px #ccc;
}
:global(.right5),
:global(.right10),
:global(.right20) {
display: inline;
&::after {
content: ' ';
display: inline-block;
}
}
:global(.right5)::after {
margin-right: 5px;
}
:global(.right10)::after {
margin-right: 10px;
}
:global(.right20)::after {
margin-right: 20px;
}
blockquote {
padding: 0.5em 1em;
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
margin: 20px 0;
}
code {
padding: 0.2em 0.4em;
margin: 0 0.5em;
font-size: 85%;
background-color: rgba(27, 31, 35, 0.05);
border-radius: 3px;
font-family: Cascadia Code, Source Code Variable, SFMono-Regular, Consolas, Liberation Mono, Menlo,
monospace;
}
:global(.ant-comment-content) {
:global(.ant-form-item-label) > label {
color: rgba(0, 0, 0, 0.65);
}
}
.footer {
text-align: center;
line-height: 1em;
// border-top: #aaa 1px solid;
padding-top: 15px;
}

275
components/layout.tsx

@ -0,0 +1,275 @@ @@ -0,0 +1,275 @@
import React, { ComponentProps } from 'react';
import Link from 'next/link';
import Head from 'next/head';
import { withRouter } from 'next/router';
import { WithRouterProps } from 'next/dist/client/with-router';
import { Affix, Button, Layout, BackTop, Row, Divider, Form, Menu, Icon, Modal, Input } from 'antd';
const { Footer, Sider, Content } = Layout;
import { FormComponentProps } from 'antd/lib/form';
import Container from '@/components/container';
import { layout, login, logout } from '@/utils/api';
import ShowNotification from '@/utils/notification';
import { GlobalProps } from '@/utils/global';
import styles from './layout.less';
interface BasicLayoutProps
extends GlobalProps,
ComponentProps<'base'>,
FormComponentProps,
WithRouterProps {
menus: Blotter.Menu[];
beian: string;
view: number;
blog_name: string;
}
interface BasicLayoutState {
collapsed: boolean;
broken: boolean;
loginModel: boolean;
password: string;
okDisabled: boolean;
token: string;
}
class BasicLayout extends React.Component<BasicLayoutProps, BasicLayoutState> {
static async getInitialProps(args: any) {
var r = await layout();
return r;
}
constructor(props: BasicLayoutProps) {
super(props);
this.state = {
collapsed: true,
broken: false,
loginModel: false,
password: '',
okDisabled: false,
token: '',
};
// setSiteName(this.props.blog_name);
// setTitle('首页');
// console.log(this.props.menus);
}
onCollapse = (collapsed: boolean, type: string) => {
if (type == 'responsive') {
this.setState({ collapsed: true });
} else {
this.setState({ collapsed });
}
};
onBreakpoint = (broken: boolean) => {
this.setState(() => ({ broken: broken }));
this.setState(() => ({ collapsed: true }));
};
onCollapseButtonClick = () => this.setState(state => ({ collapsed: !state.collapsed }));
renderCollapseButton = () => (
<Affix
offsetTop={this.state.broken ? window.innerHeight - 60 : 20}
style={Object.assign(
{ position: 'fixed', marginLeft: 20, zIndex: 100 },
this.state.broken ? { bottom: 20 } : { top: 20 },
)}
>
<Button
type="primary"
shape="circle"
size="large"
icon={this.state.collapsed ? 'bars' : 'left'}
className="shadow"
onClick={this.onCollapseButtonClick}
/>
</Affix>
);
onLoginClick = () => {
this.setState({ loginModel: true });
};
onLogoutClick = async () => {
var r = await logout();
if (ShowNotification(r)) {
// this.props.dispatch({ type: 'token/set', token: '' });
}
};
loginOK = async () => {
// console.log(this.props.form.getFieldValue('password'));
this.setState({ okDisabled: true });
var { username, password } = this.props.form.getFieldsValue(['username', 'password']);
var r = await login(username, password);
if (ShowNotification(r)) {
this.setState({ loginModel: false, okDisabled: false });
} else {
this.setState({ okDisabled: false });
}
};
loginCancel = () => {
this.setState({ loginModel: false });
};
renderMenus = () => {
var hasLogin = this.state.token !== '';
return (
<Menu
theme="light"
selectedKeys={[this.props.router.pathname]}
mode="inline"
inlineIndent={10}
>
        
{this.props.menus.map((item: Blotter.Menu) => {
return (
<Menu.Item key={item.link}>
<Link href={item.link}>
<a>
{item.link ? <Icon type={item.icon} /> : null}
<span> {item.name}</span>
</a>
</Link>
</Menu.Item>
);
})}
{hasLogin ? (
<Menu.Item key="login" onClick={this.onLoginClick}>
<Link href="/admin">
<a>
<Icon type="setting" />
<span></span>
</a>
</Link>
</Menu.Item>
) : null}
{!hasLogin ? (
<Menu.Item key="login" onClick={this.onLoginClick}>
<Icon type="login" />
<span></span>
</Menu.Item>
) : (
<Menu.Item key="logout" onClick={this.onLogoutClick}>
<Icon type="logout" />
<span></span>
</Menu.Item>
)}
        
<Modal
title="登录"
visible={this.state.loginModel}
onOk={this.loginOK}
onCancel={this.loginCancel}
okButtonProps={{ disabled: this.state.okDisabled }}
>
<Form>
<Form.Item>
{this.props.form.getFieldDecorator('username')(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="用户名"
/>,
)}
</Form.Item>
<Form.Item>
{this.props.form.getFieldDecorator(`password`)(
<Input.Password
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="密码"
/>,
)}
</Form.Item>
</Form>
</Modal>
</Menu>
);
};
renderSider = () => {
return (
<Sider
theme="light"
trigger={null}
breakpoint="xl"
collapsible={true}
collapsed={this.state.collapsed}
onCollapse={this.onCollapse}
collapsedWidth={this.state.broken ? 0 : 80}
onBreakpoint={this.onBreakpoint}
>
<div className={styles.sider}>
<Row type="flex" justify="center" className={styles.avatar}>
<img
src="/static/img/logo.svg"
width={'100%'}
height={'100%'}
style={{ background: 'white', borderRadius: '100px' }}
/>
</Row>
<Divider className={this.state.collapsed ? styles.divider : undefined}>
<b className={styles.divider}>OhYee</b>
</Divider>
{this.renderMenus()}
</div>
</Sider>
);
};
renderFooter = () => {
return (
<Container>
<div className={styles.footer}>
<p>© 2017 {new Date().getFullYear()}</p>
<p>
<Icon type="eye" style={{ fontSize: '0.75em' }} /> 访 {this.props.view}
</p>
<p>
<a href="http://beian.miit.gov.cn/">{this.props.beian}</a>
</p>
<p>
Powered by
<a href="https://github.com/OhYee/blotter">Blotter</a>
(Go + React)
</p>
</div>
</Container>
);
};
render() {
return (
<Layout
style={{ minHeight: '100%' }}
className={this.state.collapsed ? undefined : styles.dimmed}
>
<Head>
<title>{this.props.blog_name}</title>
</Head>
<Layout
className={'shadow'}
style={{ position: 'fixed', zIndex: 100, height: '100vh', overflow: 'auto' }}
>
{this.renderSider()}
<Content>{this.renderCollapseButton()}</Content>
</Layout>
<Layout style={this.state.collapsed ? {} : {}}>
<Content>
<div className={styles.main_content}>{this.props.children}</div>
<BackTop />
</Content>
<Footer>{this.renderFooter()}</Footer>
</Layout>
</Layout>
);
}
}
export default Form.create<BasicLayoutProps>({ name: 'BasicLayout' })(withRouter(BasicLayout));

14
src/components/post_card.tsx → components/post_card.tsx

@ -2,7 +2,7 @@ import React from 'react'; @@ -2,7 +2,7 @@ import React from 'react';
import { Card, Typography, Divider, Icon, Row, Col, Skeleton } from 'antd';
const { Meta } = Card;
import Link from 'umi/link';
import Link from 'next/link';
import TagPart from '@/components/tag';
@ -25,11 +25,13 @@ class PostCard extends React.Component<PostCardProps, {}> { @@ -25,11 +25,13 @@ class PostCard extends React.Component<PostCardProps, {}> {
render_post(post: Blotter.PostCard) {
return (
<div>
<Link to={`/post/${post.url}`}>
<Title level={4} ellipsis={true}>
{post.title}
</Title>
<Paragraph>{post.abstract}</Paragraph>
<Link href={`/post/${post.url}`}>
<a>
<Title level={4} ellipsis={true}>
{post.title}
</Title>
<Paragraph>{post.abstract}</Paragraph>
</a>
</Link>
<Row>
<Col span={4}>

0
src/components/post_list.tsx → components/post_list.tsx

0
src/components/tag.less → components/tag.less

18
src/components/tag.tsx → components/tag.tsx

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import React from 'react';
import { Avatar, Tag } from 'antd';
import Link from 'umi/link';
import Link from 'next/link';
import styles from './tag.less';
// import styles from './tag.less';
interface TagProps {
tag?: Blotter.Tag;
@ -22,18 +22,20 @@ class TagPart extends React.Component<TagProps, {}> { @@ -22,18 +22,20 @@ class TagPart extends React.Component<TagProps, {}> {
render() {
return typeof this.props.tag === 'undefined' ? null : (
<Tag
className={styles.tag}
// className={styles.tag}
color={this.props.tag.color}
closable={this.props.closable}
onClose={() => {
this.props.onClose!(this.props.tag!);
}}
>
<Link to={`/tag/${this.props.tag.short}`}>
{this.props.tag.icon ? (
<Avatar size={15} shape="circle" src={this.props.tag.icon} />
) : null}
{this.props.tag.name}
<Link href={`/tag/${this.props.tag.short}`}>
<a>
{this.props.tag.icon ? (
<Avatar size={15} shape="circle" src={this.props.tag.icon} />
) : null}
{this.props.tag.name}
</a>
</Link>
</Tag>
);

1
src/components/tag_search.tsx → components/tag_search.tsx

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
import React, { ComponentProps, Ref } from 'react';
import router from 'umi/router';
import PostList from '@/components/post_list';

0
src/components/visiable.tsx → components/visiable.tsx

0
mock/.gitkeep

20
mock/avatar.ts

@ -1,20 +0,0 @@ @@ -1,20 +0,0 @@
import { requestAsync } from './../src/utils/request';
import { Request, Response } from 'express';
export default {
'GET /api/avatar': async (req: Request, res: Response) => {
var email = req.query['email'];
var data: any = await requestAsync('get', 'https://api.github.com/search/users', {
q: `${email} in:email`,
});
var avatar = '';
if (data['items'] && data['items'].length > 0) {
avatar = data['items'][0]['avatar_url'];
} else {
avatar = `https://secure.gravatar.com/avatar/null`;
}
res.send(avatar);
res.end();
},
};

61
mock/comment.ts

@ -1,61 +0,0 @@ @@ -1,61 +0,0 @@
import { Request, Response } from 'express';
const comments: { [key: string]: Blotter.Comment[] } = {
'/post/wsl2_systemd': [
{
id: 1,
email: 'oyohyee@oyohyee.com',
avatar: 'https://secure.gravatar.com/avatar/d57072b4a47209833738ecda534634da',
time: '2018-01-11 15:32:12',
content: '第一条回复',
children: [
{
id: 2,
email: 'oyohyee@oyohyee.com',
avatar: 'https://secure.gravatar.com/avatar/d57072b4a47209833738ecda534634da',
time: '2019-01-11 15:32:12',
content: '回复回复',
children: [
{
id: 3,
email: 'oyohyee@oyohyee.com',
avatar: 'https://secure.gravatar.com/avatar/d57072b4a47209833738ecda534634da',
time: '2019-09-11 15:32:12',
content: '???',
children: [],
},
],
},
{
id: 4,
email: 'oyohyee@oyohyee.com',
avatar: 'https://secure.gravatar.com/avatar/d57072b4a47209833738ecda534634da',
time: '2019-05-11 15:32:12',
content: '回复回复',
children: [],
},
],
},
{
id: 5,
email: 'oyohyee@oyohyee.com',
avatar: 'https://secure.gravatar.com/avatar/d57072b4a47209833738ecda534634da',
time: '2018-01-11 15:32:12',
content: '第二条回复',
children: [],
},
],
};
export default {
'GET /api/comment': (req: Request, res: Response) => {
var url = req.query['url'];
var comment = comments[url];
if (comment) {
res.send(comment);
} else {
res.status(404);
}
res.end();
},
};

104
mock/friends.ts

@ -1,104 +0,0 @@ @@ -1,104 +0,0 @@
export default {
'GET /api/friends': <Blotter.Friend[]>[
{
image: 'https://taifua.com/wp-content/uploads/2019/09/default.jpg',
link: 'https://taifua.com/',
name: '太傅亭',
description: '面朝大海,春暖花开',
posts: [
{
title: '腾讯课堂NEXT学院产品组实习总结',
link: 'https://taifua.com/csig-next-trainee.html',
},
{ title: 'C / C++程序编译全过程', link: 'https://taifua.com/compile.html' },
],
},
{
image: 'https://www.oyohyee.com/static/img/logo.svg',
link: 'https://www.oyohyee.com/',
name: "OhYee's Blog",
description: '',
posts: [
{
title: 'WSL2(Arch Linux)使用systemd',
link: 'http://127.0.0.1:8000/post/wsl2_systemd',
},
{ title: 'Arch Linux的使用', link: 'http://127.0.0.1:8000/post//' },
],
},
{
image: 'https://taifua.com/wp-content/uploads/2019/09/default.jpg',
link: 'https://taifua.com/',
name: '太傅亭',
description: '面朝大海,春暖花开',
posts: [
{
title: '腾讯课堂NEXT学院产品组实习总结',
link: 'https://taifua.com/csig-next-trainee.html',
},
{ title: 'C / C++程序编译全过程', link: 'https://taifua.com/compile.html' },
],
},
{
image: 'https://www.oyohyee.com/static/img/logo.svg',
link: 'https://www.oyohyee.com/',
name: "OhYee's Blog",
description: '',
posts: [],
},
{
image: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
link: 'https://ant.design/',
name: 'Ant Design',
description: '',
posts: [],
},
{
image: 'https://www.oyohyee.com/static/img/logo.svg',
link: 'https://www.oyohyee.com/',
name: "OhYee's Blog",
description: '',
posts: [],
},
{
image: 'https://taifua.com/wp-content/uploads/2019/09/default.jpg',
link: 'https://taifua.com/',
name: '太傅亭',
description: '面朝大海,春暖花开',
posts: [
{
title: '腾讯课堂NEXT学院产品组实习总结',
link: 'https://taifua.com/csig-next-trainee.html',
},
{ title: 'C / C++程序编译全过程', link: 'https://taifua.com/compile.html' },
],
},
{
image: 'https://www.oyohyee.com/static/img/logo.svg',
link: 'https://www.oyohyee.com/',
name: "OhYee's Blog",
description: '',
posts: [],
},
{
image: 'https://taifua.com/wp-content/uploads/2019/09/default.jpg',
link: 'https://taifua.com/',
name: '太傅亭',
description: '面朝大海,春暖花开',
posts: [
{
title: '腾讯课堂NEXT学院产品组实习总结',
link: 'https://taifua.com/csig-next-trainee.html',
},
{ title: 'C / C++程序编译全过程1111111111111111111111111111111111', link: 'https://taifua.com/compile.html' },
],
},
{
image: 'https://www.oyohyee.com/static/img/logo.svg',
link: 'https://www.oyohyee.com/',
name: "OhYee's Blog",
description: '',
posts: [],
},
],
};

81
mock/index.ts

@ -1,81 +0,0 @@ @@ -1,81 +0,0 @@
export default {
// 支持值为 Object 和 Array
'POST /api/index/post': {
posts: [
{
title: '这是一篇测试文章',
abstract: '这篇文章主要用于测试文章卡片',
url: '/',
view: 100,
publish_time: '2019-01-01 15:32:21',
edit_time: '2019-01-01 15:32:21',
tags: [{ name: '测试', id: 'test', icon: '', color: '' }],
head_image: '',
},
{
title: 'WSL2(Arch Linux)使用systemd',
abstract: '在WSL2中以pid 1运行systemd',
view: 383,
url: 'wsl2_systemd',
publish_time: '2019-09-11 14:39:39',
edit_time: '2019-09-11 14:39:39',
tags: [
{ id: 'wsl', name: 'WSL', icon: '', color: '' },
{
id: 'arch',
name: 'Arch',
icon: 'https://www.oyohyee.com/static/img/tags/arch.png',
color: 'blue',
},
],
head_image: '',
},
{
title: 'Arch Linux的使用',
abstract: '如何安装Arch Linux',
url: '/',
view: 100,
publish_time: '2019-01-01 15:32:21',
edit_time: '2019-01-02 15:32:21',
tags: [
{ name: '测试', id: 'test', icon: '', color: '' },
{
name: 'Arch Linux',
id: 'arch',
icon: 'https://www.oyohyee.com/static/img/tags/arch.png',
color: 'blue',
},
],
head_image: 'https://www.oyohyee.com/static/img/posts/onenote.jpg',
},
{
title:
'Arch Linux的使用长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题长标题',
abstract:
'如何安装Arch Linux长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容',
url: '/',
view: 100,
publish_time: '2019-01-01 15:32:21',
edit_time: '2019-01-02 15:32:21',
tags: [
{ name: '测试', id: 'test', icon: '', color: '' },
{
name: '微软',
id: 'Mircosoft',
icon: 'https://www.oyohyee.com/static/img/tags/mircosoft.png',
color: 'red',
},
],
head_image: '',
},
],
} as { posts: Blotter.PostCard[] },
'GET /api/site': <Blotter.Site>{
view: 1536631,
beian: '豫ICP备17000379号',
friends: [
{ name: 'ACBlackTea', link: 'http://blog.csdn.net/acblacktea' },
{ name: 'Wilbert', link: 'http://blog.csdn.net/snow_me' },
],
},
};

37
mock/menu.ts

@ -1,37 +0,0 @@ @@ -1,37 +0,0 @@
export default {
// 支持值为 Object 和 Array
'POST /api/menu': <ResponseMenu>{
menu_list: [
{
name: '首页',
icon: 'home',
link: '/',
},
{
name: '归档',
icon: 'container',
link: '/archive',
},
{
name: '标签',
icon: 'tag',
link: '/tag',
},
{
name: '评论区',
icon: 'message',
link: '/comment',
},
{
name: '关于',
icon: 'idcard',
link: '/about',
},
{
name: '后台',
icon: 'setting',
link: '/setting',
},
],
},
};

59
mock/post.ts

@ -1,59 +0,0 @@ @@ -1,59 +0,0 @@
import { Request, Response } from 'express';
const posts: { [key: string]: Blotter.Post } = {
wsl2_systemd: {
title: 'WSL2(Arch Linux)使用systemd',
abstract: '在WSL2中以pid 1运行systemd',
view: 383,
url: 'wsl2_systemd',
publish_time: '2019-09-11 14:39:39',
edit_time: '2019-09-11 14:39:39',
tags: [
{ id: 'wsl', name: 'WSL', icon: '', color: '' },
{
id: 'arch',
name: 'Arch',
icon: 'https://www.oyohyee.com/static/img/tags/arch.png',
color: 'blue',
},
],
head_image: '',
content: `<h1 id="wsl2arch-linuxsystemd">WSL2(Arch Linux)使用systemd</h1>
<h2 id="heading"></h2>
<p>WSL本身是由Windows负责运行的使<code>tree</code>systemd(deamon)nginxdockermysql等</p>
<p>ip转发systemd慢慢都会实现</p>
<p>使<a href="https://github.com/arkane-systems/genie">arkane-systems/genie</a> <br>
systemdsystemctl指令并启动服务</p>
<h2 id="heading1"></h2>
<h3 id="-net"> .NET</h3>
<p><code>.NET Core runtime</code><code>yay dotnet</code>runtime的安装即可
<code>export DOTNET_ROOT=/opt/dotnet</code></p>
<h3 id="heading2"></h3>
<p>pythonpython-markdownpython-sixpython-packagingpython-setuptoolpython-attrs等依赖包
使yay安装上他们使pip安装貌似也不行</p>
<p> <code>make</code></p>
<h3 id="genie">genie</h3>
<p>使 <code>yay -S genie-systemd</code> <code>genie</code> 退</p>
<h2 id="heading3">使</h2>
<p><code>genie</code> </p>
<p><code>genie -i</code> systemd进程
<code>genie -s</code> systemd进程
<code>genie -c &lt;command&gt;</code> systemd进程</p>
<p>docker使<code>genie -s</code><code>sudo systemctl start docker</code>
<code>pstree</code><code>systemd</code>
退<code>systemd</code></p>`,
},
};
export default {
'GET /api/post': (req: Request, res: Response) => {
var url = req.query['url'];
var post = posts[url];
if (post) {
res.send(post);
} else {
res.status(404);
}
res.end();
},
};

2
next-env.d.ts vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />

75
next.config.js

@ -0,0 +1,75 @@ @@ -0,0 +1,75 @@
const withTypescript = require('@zeit/next-typescript');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const withLess = require('@zeit/next-less');
const withSass = require('@zeit/next-sass');
const withCss = require('@zeit/next-css');
const withPlugins = require('next-compose-plugins');
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const withTM = require('next-transpile-modules');
const cssLoaderGetLocalIdent = require('css-loader/lib/getLocalIdent.js');
// with ant design
// https://juejin.im/post/5cc74b925188252e741cce09
withAntd = (nextConfig = {}) => {
return Object.assign({}, nextConfig, {
lessLoaderOptions: {
javascriptEnabled: true,
},
cssModules: true,
cssLoaderOptions: {
camelCase: true,
localIdentName: '[local]___[hash:base64:5]',
getLocalIdent: (context, localIdentName, localName, options) => {
let hz = context.resourcePath.replace(context.rootContext, '');
if (/node_modules/.test(hz)) {
return localName;
} else {
return cssLoaderGetLocalIdent(context, localIdentName, localName, options);
}
},
},
webpack(config, options) {
if (config.externals) {
const includes = [/antd/];
config.externals = config.externals.map(external => {
if (typeof external !== 'function') return external;
return (ctx, req, cb) => {
return includes.find(include =>
req.startsWith('.') ? include.test(path.resolve(ctx, req)) : include.test(req),
)
? cb()
: external(ctx, req, cb);
};
});
}
return typeof nextConfig.webpack === 'function'
? nextConfig.webpack(config, options)
: config;
},
});
};
module.exports = withPlugins([withAntd, withTM, withLess, withCss, withSass], {
serverRuntimeConfig: {
//这里的配置项只能在服务端获取到,在浏览器端是获取不到的
},
publicRuntimeConfig: {
//这里的配置既可以服务端获取到,也可以在浏览器端获取到
},
webpack: (config, options) => {
// TODO: Remove after next.js update
// https://github.com/zeit/next.js/issues/7779
config.resolve.alias['@'] = path.resolve(__dirname, '.');
// TypeScript check
// config.plugins.push(
// new ForkTsCheckerWebpackPlugin({
// tsconfig: './tsconfig.json',
// }),
// );
return config;
},
});

82
package.json

@ -1,65 +1,35 @@ @@ -1,65 +1,35 @@
{
"private": true,
"scripts": {
"start": "umi dev",
"build": "umi build",
"test": "umi test",
"server": "cross-env NODE_ENV=development COMPRESS=none concurrently \"umi dev\" \"nodemon server.js\"",
"lint:es": "eslint --ext .js src mock tests",
"lint:ts": "tslint \"src/**/*.ts\" \"src/**/*.tsx\"",
"precommit": "lint-staged"
"dev": "next -p 50002",
"build": "next build",
"start": "next start -p 50002"
},
"dependencies": {
"@types/express": "^4.17.1",
"@types/mongodb": "^3.3.14",
"@types/react-responsive": "^8.0.2",
"antd": "^3.26.7",
"@material-ui/core": "^4.9.2",
"@types/antd": "^1.0.0",
"@types/node": "^13.7.0",
"@types/react": "^16.9.19",
"@zeit/next-css": "^1.0.1",
"@zeit/next-less": "^1.0.1",
"@zeit/next-sass": "^1.0.1",
"@zeit/next-typescript": "^1.1.1",
"antd": "^3.26.9",
"axios": "^0.19.2",
"dva": "^2.4.1",
"mongodb": "^3.5.2",
"nodemon": "^2.0.2",
"babel-plugin-import": "^1.13.0",
"css": "^2.2.4",
"fork-ts-checker-webpack-plugin": "^4.0.3",
"less": "^3.10.3",
"next": "^9.2.1",
"next-compose-plugins": "^2.2.0",
"next-transpile-modules": "^3.0.2",
"null-loader": "^3.0.0",
"react": "^16.12.0",
"react-dom": "^16.8.6",
"react-dom": "^16.12.0",
"react-responsive": "^8.0.3",
"umi-request": "^1.2.18",
"umi-server": "^1.2.2"
},
"devDependencies": {
"@types/jest": "^25.1.0",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.9.5",
"@types/react-test-renderer": "^16.0.3",
"babel-eslint": "^10.0.3",
"babel-plugin-import": "^1.13.0",
"concurrently": "^5.1.0",
"cross-env": "^7.0.0",
"eslint": "^6.5.1",
"eslint-config-umi": "^1.4.0",
"eslint-plugin-flowtype": "^4.3.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.11.1",
"husky": "^4.2.1",
"lint-staged": "^10.0.4",
"react-test-renderer": "^16.12.0",
"tslint": "^6.0.0",
"tslint-eslint-rules": "^5.4.0",
"tslint-react": "^4.2.0",
"umi": "^2.13.3",
"umi-plugin-react": "^1.15.2",
"umi-types": "^0.5.12"
},
"lint-staged": {
"*.{ts,tsx}": [
"tslint --fix",
"git add"
],
"*.{js,jsx}": [
"eslint --fix",
"git add"
]
},
"engines": {
"node": ">=8.0.0"
"react-router": "^5.1.2",
"sass": "^1.25.0",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"typescript": "^3.7.5"
}
}
}

49
pages/_app.tsx

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
import React from 'react';
import App from 'next/app';
import BasicLayout from '@/components/layout';
const Layout = BasicLayout as any;
export default class MyApp extends App {
constructor(props: any) {
super(props);
console.log('APP', props);
}
static pageProps = {
layoutProps: {},
componentProps: {},
};
static async getInitialProps({ Component, router, ctx }) {
const isServer = !!ctx.req;
try {
var layoutPromise = {};
var componentPromise = {};
console.log(Layout.getInitialProps);
if (isServer && Layout.getInitialProps) {
layoutPromise = Layout.getInitialProps(ctx);
}
if (Component.getInitialProps) {
componentPromise = Component.getInitialProps(ctx);
}
if (isServer) {
MyApp.pageProps.layoutProps = await layoutPromise;
}
MyApp.pageProps.componentProps = await componentPromise;
} catch (e) {
console.log(e);
}
return { pageProps: MyApp.pageProps };
}
render() {
const { Component, pageProps } = this.props;
return (
<Layout {...pageProps.layoutProps}>
<Component {...pageProps.layoutProps} {...pageProps.componentProps} />
</Layout>
);
}
}

34
pages/about.tsx

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
import React, { ComponentProps } from 'react';
import Head from 'next/head';
import { Card } from 'antd';
import Container from '@/components/container';
import { GlobalProps } from '@/utils/global';
interface AboutPageProps extends GlobalProps, ComponentProps<'base'> {}
interface AboutPageState {}
export class AboutPage extends React.Component<AboutPageProps, AboutPageState> {
static defaultProps = {};
constructor(props: any) {
super(props);
this.state = {};
}
render() {
return (
<Container>
<Head>
<title>{`关于我|${this.props.blog_name}`}</title>
</Head>
<Card></Card>
</Container>
);
}
}
export default AboutPage;

13
src/pages/admin/index.tsx → pages/admin/index.tsx

@ -1,12 +1,14 @@ @@ -1,12 +1,14 @@
import React, { ComponentProps } from 'react';
import router from 'umi/router';
import Head from 'next/head';
import { Card } from 'antd';
import PostList from '@/components/post_list';
import { InitialPropsParam } from '@/utils/api';
import Container from '@/components/container';
interface AdminIndexProps extends ComponentProps<'base'> {}
import { GlobalProps } from '@/utils/global';
interface AdminIndexProps extends GlobalProps, ComponentProps<'base'> {}
interface AdminIndexState {}
@ -23,6 +25,9 @@ class AdminIndex extends React.Component<AdminIndexProps, AdminIndexState> { @@ -23,6 +25,9 @@ class AdminIndex extends React.Component<AdminIndexProps, AdminIndexState> {
render() {
return (
<Container>
<Head>
<title>{`后台|${this.props.blog_name}`}</title>
</Head>
<Card></Card>
</Container>
);

78
src/pages/admin/post.tsx → pages/admin/post.tsx

@ -1,10 +1,9 @@ @@ -1,10 +1,9 @@
import React, { ComponentProps } from 'react';
import router from 'umi/router';
import PostList from '@/components/post_list';
import { withRouter, RouteComponentProps } from 'react-router';
import { InitialPropsParam, markdown, tagsSearch, adminPost } from '@/utils/api';
import Container from '@/components/container';
import Head from 'next/head';
import { withRouter } from 'next/router';
import { WithRouterProps } from 'next/dist/client/with-router';
import {
Card,
Row,
@ -19,17 +18,26 @@ import { @@ -19,17 +18,26 @@ import {
Divider,
} from 'antd';
import { FormComponentProps } from 'antd/lib/form';
import { waitUntil } from '@/utils/debounce';
import moment from 'moment';
import MediaQuery from 'react-responsive';
import { dimensionMaxMap } from '@/utils/responsive';
import Container from '@/components/container';
import TagSearch from '@/components/tag_search';
import { parseStringParams } from '@/utils/parse';
import { waitUntil } from '@/utils/debounce';
import { markdown, adminPost } from '@/utils/api';
import { dimensionMaxMap } from '@/utils/responsive';
import styles from '@/pages/post/post.less';
import moment from 'moment';
import withRoute from 'umi/router';
interface PostEditProps extends ComponentProps<'base'>, FormComponentProps, RouteComponentProps {}
import { GlobalProps } from '@/utils/global';
interface PostEditProps
extends GlobalProps,
ComponentProps<'base'>,
FormComponentProps,
WithRouterProps {}
interface PostEditState {
raw: string;
@ -62,9 +70,10 @@ class PostEdit extends React.Component<PostEditProps, PostEditState> { @@ -62,9 +70,10 @@ class PostEdit extends React.Component<PostEditProps, PostEditState> {
}
getData = async () => {
var url = parseStringParams('url', this.props.location.search);
var url = this.props.router.query.url as string;
var r = await adminPost(url);
this.props.form.setFieldsValue({
// this.props.form.setFieldsValue(;
var forms = {
id: r.id,
title: r.title,
url: r.url,