├── public
├── favicon.ico
├── manifest.json
└── index.html
├── src
├── assets
│ └── duck.jpg
├── components
│ ├── Button
│ │ └── index.js
│ ├── Grid
│ │ ├── Row.js
│ │ ├── index.js
│ │ └── Col.js
│ ├── ScrollToTop
│ │ └── index.js
│ ├── PointOutContent
│ │ └── index.js
│ ├── Footer
│ │ └── index.js
│ ├── ArticleDirectory
│ │ ├── Styled.js
│ │ └── index.js
│ ├── Blog
│ │ ├── Styled.js
│ │ ├── index.js
│ │ └── markdown.css
│ ├── Container
│ │ └── index.js
│ ├── Loader
│ │ └── index.js
│ ├── ContentLoader
│ │ └── index.js
│ ├── BlogList
│ │ ├── Styled.js
│ │ └── index.js
│ ├── Tag
│ │ └── index.js
│ ├── ResumeContent
│ │ ├── index.js
│ │ └── markdown.css
│ ├── TopNav
│ │ └── index.js
│ └── ProfileCard
│ │ └── index.js
├── reducers
│ ├── index.js
│ ├── blog.js
│ ├── resume.js
│ ├── profile.js
│ └── blogList.js
├── index.js
├── reboot.css
├── store.js
├── constants
│ └── index.js
├── App.js
├── utils
│ └── index.js
└── registerServiceWorker.js
├── config
├── jest
│ ├── fileTransform.js
│ └── cssTransform.js
├── polyfills.js
├── paths.js
├── env.js
├── webpackDevServer.config.js
├── webpack.config.dev.js
└── webpack.config.prod.js
├── .gitignore
├── scripts
├── test.js
├── start.js
└── build.js
├── package.json
└── README.md
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaleGZL/zalegzl-website/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/duck.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaleGZL/zalegzl-website/HEAD/src/assets/duck.jpg
--------------------------------------------------------------------------------
/src/components/Button/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const Button = styled.button`
5 | background-color: red;
6 | `
7 |
8 | export default Button
9 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import blogList from './blogList'
3 | import blog from './blog'
4 | import resume from './resume'
5 | import profile from './profile'
6 |
7 | export default combineReducers({
8 | blogList,
9 | blog,
10 | resume,
11 | profile
12 | })
13 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/en/webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/src/components/Grid/Row.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const Row = styled.div`
4 | /* 清除浮动 */
5 | &::after {
6 | content: '';
7 | clear: both;
8 | display: table;
9 | }
10 |
11 | /* 是网格布局具有相同的高度 */
12 | display: -webkit-box;
13 | display: -webkit-flex;
14 | display: -ms-flexbox;
15 | display: flex;
16 | `
17 |
18 | export default Row
19 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "ZALE_BLOG",
3 | "name": "guozeling's blog",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/Grid/index.js:
--------------------------------------------------------------------------------
1 | import Row from './Row'
2 | import Col from './Col'
3 |
4 | const Grid = {
5 | Row,
6 | Col
7 | }
8 |
9 | // 网格布局 API
10 | // Row (行)
11 |
12 | // Col (列) 元素所占用12格中的多少格
13 | // xs: < 576px or 适配所有屏幕宽度
14 | // sm: >= 576px
15 | // md: >= 768px
16 | // lg: >= 992px
17 | // xl: >= 1200px
18 | // gutter: left and right padding (default 15px)
19 |
20 | export default Grid
21 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { Provider } from 'react-redux'
4 | import { BrowserRouter as Router } from 'react-router-dom'
5 | import store from './store'
6 | import App from './App'
7 | // import registerServiceWorker from './registerServiceWorker'
8 | // registerServiceWorker()
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 | ,
16 | document.getElementById('root')
17 | )
18 |
--------------------------------------------------------------------------------
/src/components/ScrollToTop/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Styled from 'styled-components'
3 | import ScrollToTop from 'react-scroll-up'
4 | const StyledScrollUp = Styled.div`
5 | font-size: 30px;
6 | color: #1c93e0;
7 | &:hover {
8 | opacity: 0.6;
9 | }
10 | `
11 |
12 | const ScrollToTopCustom = () => (
13 |
14 |
15 |
16 |
17 |
18 | )
19 |
20 | export default ScrollToTopCustom
--------------------------------------------------------------------------------
/src/components/PointOutContent/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Styled from 'styled-components'
4 |
5 | const PointOutContentContainer = Styled.div`
6 | margin-top: 20px;
7 | text-align: center;
8 | font-size: 1.4rem;
9 | color: rgba(0, 0, 0, 0.65);
10 | `
11 |
12 | const PointOutContent = ({ text }) => (
13 | {text}
14 | )
15 |
16 | PointOutContent.proptypes = {
17 | text: PropTypes.string
18 | }
19 |
20 | export default PointOutContent
21 |
--------------------------------------------------------------------------------
/src/reboot.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 12px;
3 | font-family: -apple-system, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
4 | word-wrap: break-word;
5 | color: #333;
6 | background-color: #F4F5F5;
7 | position: relative;
8 | min-height: 100%;
9 | }
10 |
11 | *,
12 | *::before,
13 | *::after {
14 | box-sizing: border-box;
15 | }
16 |
17 | body {
18 | margin: 0 0 100px;
19 | padding-top: 50px;
20 | }
21 |
22 | a {
23 | text-decoration: none;
24 | cursor: pointer;
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Styled from 'styled-components'
3 |
4 | const StyledFooter = Styled.div`
5 | padding: 60px 0 10px;
6 | text-align: center;
7 | font-size: 1.3rem;
8 | font-weight: 500;
9 | line-height: 2;
10 | color: #3c3b3b;
11 |
12 | position: absolute;
13 | left: 0;
14 | bottom: 0;
15 | height: 100px;
16 | width: 100%;
17 | `
18 |
19 | const Footer = () => (
20 |
21 | @2018 ZALE
22 |
23 | )
24 |
25 | export default Footer
26 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux'
2 | import thunk from 'redux-thunk'
3 | import rootReducer from './reducers'
4 |
5 | const initialState = {}
6 | const middlewares = [thunk]
7 | const enhancers = []
8 |
9 | if (process.env.NODE_ENV === 'development') {
10 | const devToolsExtension = window.devToolsExtension
11 |
12 | if (typeof devToolsExtension === 'function') {
13 | enhancers.push(devToolsExtension())
14 | }
15 | }
16 |
17 | const composedEnhancers = compose(applyMiddleware(...middlewares), ...enhancers)
18 |
19 | export default createStore(rootReducer, initialState, composedEnhancers)
20 |
--------------------------------------------------------------------------------
/src/components/ArticleDirectory/Styled.js:
--------------------------------------------------------------------------------
1 | import Styled from 'styled-components'
2 |
3 | export const ArticleDirectoryContainer = Styled.div`
4 | background-color: #fff;
5 | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.2);
6 | color: #636363;
7 | font-weight: 300;
8 |
9 | position: -webkit-sticky;
10 | position: sticky;
11 | top: 0;
12 | `
13 |
14 | export const Title = Styled.div`
15 | font-size: 1.25rem;
16 | font-weight: 400;
17 | text-align: center;
18 | border-bottom: 1px solid #e8e8e8;
19 | padding: 10px 0;
20 | `
21 |
22 | export const Content = Styled.div`
23 | padding: 10px 0;
24 | font-size: 1.3rem;
25 | padding-right: 15px;
26 |
27 | & a {
28 | color: #636363;
29 | }
30 | & a:hover, & a:active {
31 | color: #303030;
32 | font-weight: 400;
33 | }
34 | `
35 |
--------------------------------------------------------------------------------
/src/components/ArticleDirectory/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 | import * as Styled from './Styled'
4 |
5 | class ArticleDirectory extends Component {
6 | render() {
7 | const toc = this.props.toc
8 | return (
9 |
10 | 文章目录
11 | {toc ? (
12 |
13 |
18 |
19 | ) : null}
20 |
21 | )
22 | }
23 | }
24 |
25 | const mapStateToProps = state => ({
26 | toc: state.blog.data.toc
27 | })
28 |
29 | export default connect(mapStateToProps)(ArticleDirectory)
30 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 | const jest = require('jest');
19 | const argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI or in coverage mode
22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) {
23 | argv.push('--watch');
24 | }
25 |
26 |
27 | jest.run(argv);
28 |
--------------------------------------------------------------------------------
/src/components/Blog/Styled.js:
--------------------------------------------------------------------------------
1 | import Styled from 'styled-components'
2 |
3 | export const BlogContainer = Styled.div`
4 | background-color: #fff;
5 | margin-bottom: 1rem;
6 | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.2);
7 |
8 | color: rgba(51, 51, 51, 0.9);
9 | `
10 |
11 | export const InfoContainer = Styled.div`
12 | padding: 1.1rem 1.3rem 1rem 1.3rem;
13 | /* border-bottom: 1px solid #e8e8e8; */
14 | text-align: center;
15 | `
16 |
17 | export const Info = Styled.span`
18 | padding-right: 1.6rem;
19 | padding-bottom: 0.3rem;
20 |
21 | font-size: 1.1rem;
22 | font-weight: 400;
23 | line-height: 1.5;
24 | color: rgba(0, 0, 0, 0.45);
25 | `
26 |
27 | export const Title = Styled.div`
28 | font-size: 1.8rem;
29 | padding-bottom: 1.3rem;
30 | `
31 |
32 | export const BlogContent = Styled.div`
33 | padding: 1rem 1.3rem 2rem 1.3rem;
34 | `
35 |
--------------------------------------------------------------------------------
/src/components/Container/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import styled, { css } from 'styled-components'
3 | import { getPixelString } from '../../utils'
4 |
5 | const getMaxWidth = css`
6 | @media only screen and (min-width: 576px) {
7 | max-width: 540px;
8 | }
9 |
10 | @media only screen and (min-width: 768px) {
11 | max-width: 720px;
12 | }
13 |
14 | @media only screen and (min-width: 992px) {
15 | max-width: 960px;
16 | }
17 |
18 | @media only screen and (min-width: 1200px) {
19 | max-width: 1140px;
20 | }
21 | `
22 |
23 | const Container = styled.div`
24 | width: 100%;
25 | padding: 0 ${({ lrPadding }) => getPixelString(lrPadding, 15)};
26 | margin: 0 auto;
27 |
28 | ${({ fluid }) => (fluid ? undefined : getMaxWidth)};
29 | `
30 |
31 | Container.propTypes = {
32 | lrPadding: PropTypes.number,
33 | fluid: PropTypes.bool
34 | }
35 |
36 | export default Container
37 |
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19 | // We don't polyfill it in the browser--this is user's responsibility.
20 | if (process.env.NODE_ENV === 'test') {
21 | require('raf').polyfill(global);
22 | }
23 |
--------------------------------------------------------------------------------
/src/constants/index.js:
--------------------------------------------------------------------------------
1 | // 博客列表每页的数目
2 | const blogListPerPageCount = 5
3 | const activePaginationClassName = 'active-pagination-link'
4 | const blogListQs = 'fields=_id,title,summary,createTime,viewTimes,tags,category'
5 | const blogQs =
6 | 'fields=title,summary,createTime,viewTimes,tags,category,htmlContent,toc'
7 | const apiPrefix = '/api'
8 |
9 | const navList = [
10 | {
11 | key: 'home',
12 | to: '/blogs',
13 | name: '首页',
14 | icon: 'fas fa-home',
15 | exact: false
16 | },
17 | {
18 | key: 'categories',
19 | to: '/categories',
20 | name: '分类',
21 | icon: 'fas fa-th',
22 | exact: false
23 | },
24 | {
25 | key: 'tags',
26 | to: '/tags',
27 | name: '标签',
28 | icon: 'fas fa-tags',
29 | exact: false
30 | },
31 | {
32 | key: 'about',
33 | to: '/about',
34 | name: '关于',
35 | icon: 'fas fa-user',
36 | exact: false
37 | }
38 | ]
39 |
40 | export {
41 | blogListPerPageCount,
42 | activePaginationClassName,
43 | blogListQs,
44 | navList,
45 | apiPrefix,
46 | blogQs
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/Grid/Col.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 | import PropTypes from 'prop-types'
3 | import { getPixelString } from '../../utils'
4 |
5 | const getWidthString = span => {
6 | if (span === undefined) {
7 | return undefined
8 | } else if (span === 0) {
9 | return 'display: none;'
10 | }
11 |
12 | const width = span / 12 * 100
13 | return `width: ${width}%;`
14 | }
15 |
16 | const Col = styled.div`
17 | float: left;
18 | padding: 0 ${({ gutter }) => getPixelString(gutter, 15)};
19 |
20 | @media only screen and (min-width: 1px) {
21 | display: block;
22 | width: 100%;
23 | ${({ xs }) => getWidthString(xs)};
24 | }
25 |
26 | @media only screen and (min-width: 576px) {
27 | display: block;
28 | ${({ sm }) => getWidthString(sm)};
29 | }
30 |
31 | @media only screen and (min-width: 768px) {
32 | display: block;
33 | ${({ md }) => getWidthString(md)};
34 | }
35 |
36 | @media only screen and (min-width: 992px) {
37 | display: block;
38 | ${({ lg }) => getWidthString(lg)};
39 | }
40 |
41 | @media only screen and (min-width: 1200px) {
42 | display: block;
43 | ${({ xl }) => getWidthString(xl)};
44 | }
45 | `
46 |
47 | Col.propTypes = {
48 | xs: PropTypes.number,
49 | sm: PropTypes.number,
50 | md: PropTypes.number,
51 | lg: PropTypes.number,
52 | xl: PropTypes.number,
53 | gutter: PropTypes.number
54 | }
55 |
56 | export default Col
57 |
--------------------------------------------------------------------------------
/src/reducers/blog.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { apiPrefix } from '../constants'
3 |
4 | const types = {
5 | // 获取博客信息
6 | START_GET_BLOG: 'blog/START_GET_BLOG',
7 | SUCCESS_GET_BLOG: 'blog/SUCCESS_GET_BLOG',
8 | FAILURE_GET_BLOG: 'blog/FAILURE_GET_BLOG'
9 | }
10 |
11 | const initState = {
12 | status: '',
13 | data: {}
14 | }
15 |
16 | export default (state = initState, action) => {
17 | switch (action.type) {
18 | case types.START_GET_BLOG:
19 | return {
20 | ...state,
21 | status: 'pending'
22 | }
23 | case types.SUCCESS_GET_BLOG:
24 | return {
25 | status: 'success',
26 | data: action.data
27 | }
28 | case types.FAILURE_GET_BLOG:
29 | return {
30 | ...state,
31 | status: 'failure'
32 | }
33 | default:
34 | return state
35 | }
36 | }
37 |
38 | const startGetBlog = () => ({
39 | type: types.START_GET_BLOG
40 | })
41 |
42 | const successGetBlog = blog => ({
43 | type: types.SUCCESS_GET_BLOG,
44 | data: blog
45 | })
46 |
47 | const failureGetBlog = () => ({
48 | type: types.FAILURE_GET_BLOG
49 | })
50 |
51 | const requestGetBlog = id => dispatch => {
52 | dispatch(startGetBlog())
53 |
54 | // 发起请求
55 | return axios
56 | .get(`${apiPrefix}/blogs/${id}`)
57 | .then(response => {
58 | const blog = response.data.data
59 | dispatch(successGetBlog(blog))
60 | return 'success'
61 | })
62 | .catch(error => {
63 | console.log(error.response)
64 | dispatch(failureGetBlog())
65 | return 'failure'
66 | })
67 | }
68 |
69 | export { requestGetBlog }
70 |
--------------------------------------------------------------------------------
/src/reducers/resume.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { apiPrefix } from '../constants'
3 |
4 | const types = {
5 | START_GET_RESUME: 'resume/START_GET_RESUME',
6 | SUCCESS_GET_RESUME: 'resume/SUCCESS_GET_RESUME',
7 | FAILURE_GET_RESUME: 'resume/FAILURE_GET_RESUME'
8 | }
9 |
10 | const initState = {
11 | status: '',
12 | htmlContent: ''
13 | }
14 |
15 | export default (state = initState, action) => {
16 | switch (action.type) {
17 | case types.START_GET_RESUME:
18 | return {
19 | ...state,
20 | status: 'pending'
21 | }
22 | case types.SUCCESS_GET_RESUME:
23 | return {
24 | htmlContent: action.data,
25 | status: 'success'
26 | }
27 | case types.FAILURE_GET_RESUME:
28 | return {
29 | ...state,
30 | status: 'failure'
31 | }
32 | default:
33 | return state
34 | }
35 | }
36 |
37 | const startGetResume = () => ({
38 | type: types.START_GET_RESUME
39 | })
40 |
41 | const successGetResume = htmlContent => ({
42 | type: types.SUCCESS_GET_RESUME,
43 | data: htmlContent
44 | })
45 |
46 | const failureGetResume = () => ({
47 | type: types.FAILURE_GET_RESUME
48 | })
49 |
50 | const requestGetResume = () => dispatch => {
51 | dispatch(startGetResume())
52 |
53 | return axios
54 | .get(`${apiPrefix}/resumes`)
55 | .then(response => {
56 | const { htmlContent } = response.data.data
57 | dispatch(successGetResume(htmlContent))
58 | return 'success'
59 | })
60 | .catch(error => {
61 | console.log(error.response)
62 | dispatch(failureGetResume())
63 | return 'failure'
64 | })
65 | }
66 |
67 | export { requestGetResume }
68 |
--------------------------------------------------------------------------------
/src/components/Loader/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import PropTypes from 'prop-types'
4 | import { getPixelString } from '../../utils'
5 |
6 | const StyledLoaderContainer = styled.div`
7 | width: 100%;
8 | min-height: ${({ height }) => getPixelString(height, null)};
9 | padding-top: ${({ paddingTB }) => getPixelString(paddingTB, null)};
10 | padding-bottom: ${({ paddingTB }) => getPixelString(paddingTB, null)};
11 | `
12 |
13 | const StyledLoader = styled.div`
14 | border: 10px solid #fff;
15 | border-radius: 50%;
16 | border-top: 10px solid #3498db;
17 | border-bottom: 10px solid #3498db;
18 | margin: 0 auto;
19 | width: ${({ size }) => getPixelString(size, null)};
20 | height: ${({ size }) => getPixelString(size, null)};
21 | -webkit-animation: spin 2s linear infinite; /* Safari */
22 | animation: spin 2s linear infinite;
23 |
24 | /* Safari */
25 | @-webkit-keyframes spin {
26 | 0% {
27 | -webkit-transform: rotate(0deg);
28 | }
29 | 100% {
30 | -webkit-transform: rotate(360deg);
31 | }
32 | }
33 |
34 | @keyframes spin {
35 | 0% {
36 | transform: rotate(0deg);
37 | }
38 | 100% {
39 | transform: rotate(360deg);
40 | }
41 | }
42 | `
43 |
44 | const Loader = ({ minHeight, size }) => {
45 | const paddingTB = (minHeight - size) / 2
46 | return (
47 |
48 |
49 |
50 | )
51 | }
52 |
53 | Loader.propTypes = {
54 | minHeight: PropTypes.number.isRequired,
55 | size: PropTypes.number.isRequired
56 | }
57 |
58 | export default Loader
59 |
--------------------------------------------------------------------------------
/src/components/ContentLoader/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Styled from 'styled-components'
4 |
5 | const StyledContentLoaderContaner = Styled.div`
6 | background-color: #fff;
7 | height: ${({ height }) => height + 'px'};
8 | border: 1px solid #fff;
9 | padding: 0 20px;
10 | margin-bottom: 20px;
11 | overflow: hidden;
12 | `
13 |
14 | const StyledContentContainer = Styled.div`
15 | box-sizing: border-box;
16 | margin-top: 20px;
17 | margin-bottom: 20px;
18 | width: ${({ width }) => width + '%'};
19 | height: 25px;
20 | `
21 |
22 | const StyledContent = Styled.div`
23 | width: 100%;
24 | height: 100%;
25 | background-color: #f1f1f1;
26 | `
27 |
28 | const getWidthArrayByHeight = height => {
29 | const number = Math.floor(height / 50)
30 | const arr = []
31 | for (let i = 0; i < number; i++) {
32 | arr.push(Math.floor(Math.random() * 90 + 10))
33 | }
34 | return arr
35 | }
36 |
37 | class ContentLoader extends React.Component {
38 | constructor(props) {
39 | super(props)
40 |
41 | const height = this.props.height
42 | this.state = {
43 | height,
44 | WidthArrayByHeight: getWidthArrayByHeight(height)
45 | }
46 | }
47 |
48 | render() {
49 | return (
50 | {this.state.WidthArrayByHeight.map((width, index) => (
51 |
52 |
53 |
54 | ))}
55 | )
56 | }
57 | }
58 |
59 |
60 | ContentLoader.propTypes = {
61 | height: PropTypes.number
62 | }
63 |
64 | export default ContentLoader
--------------------------------------------------------------------------------
/src/reducers/profile.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { apiPrefix } from '../constants'
3 |
4 | const types = {
5 | START_GET_PROFILE_INFO: 'profile/START_GET_PROFILE_INFO',
6 | SUCCESS_GET_PROFILE_INFO: 'profile/SUCCESS_GET_PROFILE_INFO',
7 | FAILURE_GET_PROFILE_INFO: 'profile/FAILURE_GET_PROFILE_INFO'
8 | }
9 |
10 | const initState = {
11 | status: '',
12 | blogCount: 0,
13 | tagCount: 0,
14 | categoryCount: 0
15 | }
16 |
17 | export default (state = initState, action) => {
18 | switch (action.type) {
19 | case types.START_GET_PROFILE_INFO:
20 | return {
21 | ...state,
22 | status: 'pending'
23 | }
24 | case types.SUCCESS_GET_PROFILE_INFO:
25 | return {
26 | status: 'success',
27 | ...action.data
28 | }
29 | case types.FAILURE_GET_PROFILE_INFO:
30 | return {
31 | ...state,
32 | status: 'failure'
33 | }
34 | default:
35 | return state
36 | }
37 | }
38 |
39 | const startGetProfileInfo = () => ({
40 | type: types.START_GET_PROFILE_INFO
41 | })
42 |
43 | const successGetProfileInfo = data => ({
44 | type: types.SUCCESS_GET_PROFILE_INFO,
45 | data
46 | })
47 |
48 | const failureGetProfileInfo = () => ({
49 | type: types.FAILURE_GET_PROFILE_INFO
50 | })
51 |
52 | const requestGetProfileInfo = () => dispatch => {
53 | dispatch(startGetProfileInfo())
54 |
55 | return axios
56 | .get(`${apiPrefix}/profiles`)
57 | .then(response => {
58 | const data = response.data.data
59 | dispatch(successGetProfileInfo(data))
60 | return 'success'
61 | })
62 | .catch(error => {
63 | console.log(error.response)
64 | dispatch(failureGetProfileInfo())
65 | return 'failure'
66 | })
67 | }
68 |
69 | export { requestGetProfileInfo }
70 |
--------------------------------------------------------------------------------
/src/components/BlogList/Styled.js:
--------------------------------------------------------------------------------
1 | import Styled from 'styled-components'
2 | import { activePaginationClassName } from '../../constants'
3 |
4 | export const Container = Styled.div`
5 | background-color: #fff;
6 | padding: 1.1rem 1.3rem;
7 | margin-bottom: 1rem;
8 |
9 | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.2);
10 | `
11 |
12 | export const Title = Styled.h4`
13 | margin: 0.3rem 0 0.7rem 0;
14 | overflow: hidden;
15 |
16 | font-size: 1.4rem;
17 | font-weight: 500;
18 | line-height: 1.2;
19 |
20 | & a:hover,
21 | & a:active {
22 | color: #1890ff;
23 | }
24 |
25 | & a {
26 | color: rgba(0, 0, 0, 0.65)
27 | }
28 | `
29 |
30 | export const Summary = Styled.div`
31 | font-size: 1.15rem;
32 | font-weight: 400;
33 | line-height: 1.6;
34 | color: rgba(0, 0, 0, 0.45);
35 | margin-bottom: 0.5rem;
36 | `
37 |
38 | export const InfoContainer = Styled.div`
39 | /* 清除浮动 */
40 | &::after {
41 | content: '';
42 | clear: both;
43 | display: table;
44 | }
45 | `
46 |
47 | export const Info = Styled.div`
48 | float: left;
49 | display: block;
50 | padding-right: 1.6rem;
51 | padding-bottom: 0.3rem;
52 |
53 | font-size: 1.1rem;
54 | font-weight: 400;
55 | line-height: 1.5;
56 | color: rgba(0, 0, 0, 0.45);
57 | `
58 |
59 | export const Pagination = Styled.div`
60 | padding: 10px 0;
61 | text-align: center;
62 |
63 | & a {
64 | color: black;
65 | display: inline-block;
66 | padding: 8px 16px;
67 | text-decoration: none;
68 | transition: background-color 0.3s;
69 | border: 1px solid #ddd;
70 | margin: 0 1px;
71 | }
72 |
73 | & a:hover:not(.${activePaginationClassName}) {
74 | background-color: #ddd;
75 | }
76 |
77 | & a.${activePaginationClassName} {
78 | color: white;
79 | background-color: #1e90ff;
80 | }
81 | `
82 |
--------------------------------------------------------------------------------
/src/reducers/blogList.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { blogListQs, apiPrefix } from '../constants'
3 |
4 | const types = {
5 | // 获取博客列表
6 | START_GET_BLOG_LIST: 'blogList/START_GET_BLOG_LIST',
7 | SUCCESS_GET_BLOG_LIST: 'blogList/SUCCESS_GET_BLOG_LIST',
8 | FAILURE_GET_BLOG_LIST: 'blogList/FAILURE_GET_BLOG_LIST'
9 | }
10 |
11 | const initState = {
12 | status: '',
13 | list: [],
14 | amount: 0
15 | }
16 |
17 | export default (state = initState, action) => {
18 | switch (action.type) {
19 | case types.START_GET_BLOG_LIST:
20 | return {
21 | ...state,
22 | status: 'pending'
23 | }
24 | case types.SUCCESS_GET_BLOG_LIST:
25 | return {
26 | status: 'success',
27 | list: action.data.list,
28 | amount: action.data.amount
29 | }
30 | case types.FAILURE_GET_BLOG_LIST:
31 | return {
32 | ...state,
33 | status: 'failure'
34 | }
35 | default:
36 | return state
37 | }
38 | }
39 |
40 | const startGetBlogList = () => ({
41 | type: types.START_GET_BLOG_LIST
42 | })
43 |
44 | const successGetBlogList = (list, amount) => ({
45 | type: types.SUCCESS_GET_BLOG_LIST,
46 | data: {
47 | list,
48 | amount
49 | }
50 | })
51 |
52 | const failureGetBlogList = () => ({
53 | type: types.FAILURE_GET_BLOG_LIST
54 | })
55 |
56 | // 请求获取博客列表
57 | const requestGetBlogList = () => dispatch => {
58 | dispatch(startGetBlogList())
59 |
60 | return axios
61 | .get(`${apiPrefix}/blogs?${blogListQs}`)
62 | .then(response => {
63 | const { blogs, count } = response.data.data
64 | dispatch(successGetBlogList(blogs, count))
65 | return 'success'
66 | })
67 | .catch(error => {
68 | console.log(error.response)
69 | dispatch(failureGetBlogList())
70 | return 'failure'
71 | })
72 | }
73 |
74 | export { requestGetBlogList }
75 |
--------------------------------------------------------------------------------
/src/components/Tag/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styled from 'styled-components'
3 | import PropTypes from 'prop-types'
4 | import { colorStyle, colorName } from '../../utils'
5 |
6 | const StyledTag = styled.div`
7 | display: inline-block;
8 | padding: 0 0.5833rem;
9 | margin-right: 0.6667rem;
10 | font-size: 1rem;
11 | line-height: 1.5;
12 | height: 1.833rem;
13 | border-radius: 4px;
14 | border: 1px solid #d9d9d9;
15 |
16 | /* 标签的默认颜色样式 */
17 | background-color: #fafafa;
18 | & a {
19 | color: rgba(0, 0, 0, 0.65);
20 | }
21 |
22 | /* 根据color的值来设置颜色样式 */
23 | & a {
24 | color: ${({ color }) => color && colorStyle[color]['color']};
25 | }
26 | color: ${({ color }) => color && colorStyle[color]['color']};
27 |
28 | background-color: ${({ color }) =>
29 | color && colorStyle[color]['background-color']};
30 | border-color: ${({ color }) => color && colorStyle[color]['border-color']};
31 | `
32 |
33 | class Tag extends Component {
34 | constructor(props) {
35 | super(props)
36 |
37 | let color = undefined
38 | if (props.random) {
39 | color = colorName[Math.floor((Math.random() * 100) % 11)]
40 | } else if (props.color) {
41 | color = props.color
42 | }
43 | this.state = { color }
44 | }
45 |
46 | render() {
47 | const { to, children, isLink, onClick } = this.props
48 | return (
49 |
50 | {isLink ? (
51 |
52 | {children}
53 |
54 | ) : (
55 | {children}
56 | )}
57 |
58 | )
59 | }
60 | }
61 | Tag.propTypes = {
62 | color: PropTypes.string,
63 | random: PropTypes.bool,
64 | to: PropTypes.string,
65 | children: PropTypes.node,
66 | isLink: PropTypes.bool,
67 | onClick: PropTypes.func
68 | }
69 |
70 | export default Tag
71 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
29 | ZALE的博客
30 |
31 |
32 |
33 |
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/components/ResumeContent/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'react-redux'
4 | import { bindActionCreators } from 'redux'
5 | import { requestGetResume } from '../../reducers/resume'
6 | import Styled from 'styled-components'
7 | import ContentLoader from '../ContentLoader'
8 | import PointOutContent from '../PointOutContent'
9 | import './markdown.css'
10 |
11 | export const BlogContainer = Styled.div`
12 | background-color: #fff;
13 | margin-bottom: 1rem;
14 | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.2);
15 |
16 | color: rgba(51, 51, 51, 0.9);
17 | `
18 |
19 | export const BlogContent = Styled.div`
20 | padding: 2rem 1.3rem 2rem 1.3rem;
21 | `
22 |
23 | class Blog extends Component {
24 | componentDidMount() {
25 | this.props.requestGetResume()
26 | }
27 |
28 | render() {
29 | const { status, htmlContent } = this.props
30 |
31 | // 检查数据是否处于加载阶段或加载失败
32 | if (status === '' || status === 'pending') {
33 | return
34 | } else if (status === 'failure') {
35 | return
36 | }
37 |
38 | return (
39 |
40 |
41 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | const mapStateToProps = state => ({
53 | status: state.resume.status,
54 | htmlContent: state.resume.htmlContent
55 | })
56 |
57 | const mapDispatchToProps = dispatch =>
58 | bindActionCreators(
59 | {
60 | requestGetResume
61 | },
62 | dispatch
63 | )
64 |
65 | Blog.propTypes = {
66 | status: PropTypes.string,
67 | htmlContent: PropTypes.string
68 | }
69 |
70 | export default connect(mapStateToProps, mapDispatchToProps)(Blog)
71 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
3 | import TopNav from './components/TopNav'
4 | import Container from './components/Container'
5 | import ProfileCard from './components/ProfileCard'
6 | import Grid from './components/Grid'
7 | import Footer from './components/Footer'
8 | import Blog from './components/Blog'
9 | import ArticleDirectory from './components/ArticleDirectory'
10 | import './reboot.css'
11 | import BlogList from './components/BlogList'
12 | import { navList } from './constants'
13 | import ScrollToTop from './components/ScrollToTop'
14 | import PointOutContent from './components/PointOutContent'
15 | import ResumeContent from './components/ResumeContent'
16 |
17 | const OnDevelopingContent = () =>
18 | const NotFoundContent = () =>
19 |
20 | const App = () => (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 |
49 | export default App
50 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right