├── .gitignore
├── src
├── img
│ └── touxiang.jpg
├── js
│ ├── constants
│ │ ├── actiontype.js
│ │ └── config.js
│ ├── components
│ │ ├── home
│ │ │ ├── homefooter.js
│ │ │ ├── homeheader.js
│ │ │ ├── homecard.js
│ │ │ ├── homebanner.js
│ │ │ └── homecardarea.js
│ │ ├── blog
│ │ │ ├── articlelabel.js
│ │ │ ├── rightsider.js
│ │ │ ├── articlepaging.js
│ │ │ ├── articlelist.js
│ │ │ ├── blogindex.js
│ │ │ ├── articlearea.js
│ │ │ ├── tagcard.js
│ │ │ ├── categorycard.js
│ │ │ └── articleitem.js
│ │ ├── tag
│ │ │ ├── articlelistitem.js
│ │ │ ├── tag.js
│ │ │ ├── articlelist.js
│ │ │ └── tagcard.js
│ │ ├── archive
│ │ │ ├── archiveitemlist.js
│ │ │ ├── archive.js
│ │ │ ├── archivelist.js
│ │ │ └── yearcard.js
│ │ ├── category
│ │ │ ├── articlelistitem.js
│ │ │ ├── category.js
│ │ │ ├── articlelist.js
│ │ │ └── categorycard.js
│ │ ├── demo
│ │ │ └── democard.js
│ │ └── article
│ │ │ ├── article.js
│ │ │ ├── content.js
│ │ │ └── navigation.js
│ ├── containers
│ │ ├── home.js
│ │ ├── index.js
│ │ ├── demo.js
│ │ ├── blog.js
│ │ └── about.js
│ ├── store
│ │ └── configureStore.js
│ ├── app.js
│ ├── reducers
│ │ └── index.js
│ └── actions
│ │ └── index.js
└── css
│ ├── home
│ ├── homefooter.css
│ ├── homeheader.css
│ ├── homebanner.css
│ └── homecard.css
│ ├── index.css
│ ├── archive
│ └── archive.css
│ ├── about
│ └── about.css
│ ├── demo
│ └── demo.css
│ ├── blog
│ ├── tagcard.css
│ └── articlearea.css
│ ├── layout
│ └── layout.css
│ └── article
│ └── article.css
├── index.html
├── .eslintrc
├── webpack.config.js
├── LICENSE
├── package.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/src/img/touxiang.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axuebin/react-blog/HEAD/src/img/touxiang.jpg
--------------------------------------------------------------------------------
/src/js/constants/actiontype.js:
--------------------------------------------------------------------------------
1 | export const REQUEST_ISSUES = 'REQUEST_ISSUES';
2 | export const RECEIVE_ISSUES = 'RECEIVE_ISSUES';
3 |
--------------------------------------------------------------------------------
/src/js/constants/config.js:
--------------------------------------------------------------------------------
1 | export const COLOR_BLOG_CATEGORY = '0052cc';
2 | export const COLOR_WRITING_CATEGORY = 'c5def5';
3 | export const COLOR_LABEL_CATEGORY = 'd93f0b';
4 |
--------------------------------------------------------------------------------
/src/css/home/homefooter.css:
--------------------------------------------------------------------------------
1 | .home-footer {
2 | width: 100%;
3 | height: 60px;
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | color: rgb(255, 255, 255);
8 | background-color: rgb(0, 0, 0);
9 | font-size: 14px;
10 | }
11 |
--------------------------------------------------------------------------------
/src/css/index.css:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.min.css';
2 | html, body {
3 | width: 100%;
4 | margin: 0;
5 | padding: 0;
6 | overflow: auto;
7 | }
8 |
9 | .middle{
10 | min-height: 100%;
11 | width:100%;
12 | background-color: rgb(50,50,50);
13 | }
14 |
--------------------------------------------------------------------------------
/src/js/components/home/homefooter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../../../css/home/homefooter.css';
3 |
4 | export default class HomeFooter extends React.Component {
5 | render() {
6 | return (
7 |
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/js/containers/home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import HomeBanner from '../components/home/homebanner';
3 | import HomeCardArea from '../components/home/homecardarea';
4 |
5 | export default class Home extends React.Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/js/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunkMiddleware from 'redux-thunk';
3 | import rootReducer from '../reducers/index';
4 |
5 | export default function configureStore(preloadedState) {
6 | return createStore(
7 | rootReducer,
8 | preloadedState,
9 | applyMiddleware(
10 | thunkMiddleware,
11 | ),
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/css/archive/archive.css:
--------------------------------------------------------------------------------
1 | .archive-itemlist {
2 | padding: 20px;
3 | }
4 |
5 | .archive-itemlist h2 {
6 | font-size: 1.8em;
7 | }
8 |
9 | .archive-itemlist ul {
10 | margin-top: 10px;
11 | }
12 |
13 | .archive-itemlist li {
14 | font-size: 1.3em;
15 | margin-top: 5px;
16 | }
17 |
18 | .archive-itemlist a {
19 | color: rgb(0, 0, 0);
20 | margin-left: 10px;
21 | text-decoration: none;
22 | }
23 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 | 正在载入中...
2 |
3 |
4 |
5 |
12 |
--------------------------------------------------------------------------------
/src/js/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import configureStore from './store/configureStore';
5 | import '../css/index.css';
6 | import '../css/layout/layout.css';
7 | import Index from './containers/index';
8 |
9 | const store = configureStore();
10 |
11 | ReactDOM.render(
12 |
13 |
14 | , document.getElementById('example'));
15 |
--------------------------------------------------------------------------------
/src/css/about/about.css:
--------------------------------------------------------------------------------
1 | .about-container {
2 | padding: 0px 50px;
3 | background-color: rgb(255, 255, 255);
4 | }
5 |
6 | .about-detial {
7 | height: 40%;
8 | text-align: center;
9 | max-height: 300px;
10 | margin: 20px 0px;
11 | }
12 |
13 | .about-detial ul {
14 | margin-top: 10px;
15 | }
16 |
17 | .about-detial li {
18 | font-size: 16px;
19 | line-height: 30px;
20 | }
21 |
22 | .about-detial a {
23 | text-decoration: none;
24 | color: rgb(0, 0, 0);
25 | }
26 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env":{
4 | "browser": true
5 | },
6 | "rules":{
7 | "max-len":[1,200],
8 | "linebreak-style":0,
9 | "no-console": "off",
10 | "jsx-a11y/href-no-hash": "off",
11 | "react/forbid-prop-types":"off",
12 | "react/prefer-stateless-function":"off",
13 | "class-methods-use-this": "off",
14 | "no-return-assign": 0,
15 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/js/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { REQUEST_ISSUES, RECEIVE_ISSUES } from '../constants/actiontype';
2 |
3 | function issues(state = {
4 | isFetching: false,
5 | items: [],
6 | }, action) {
7 | switch (action.type) {
8 | case REQUEST_ISSUES:
9 | return Object.assign({}, state, {
10 | isFetching: true,
11 | });
12 | case RECEIVE_ISSUES:
13 | return Object.assign({}, state, {
14 | isFetching: false,
15 | items: action.posts,
16 | });
17 | default:
18 | return state;
19 | }
20 | }
21 |
22 | export default issues;
23 |
--------------------------------------------------------------------------------
/src/js/components/blog/articlelabel.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import PropTypes from 'prop-types';
4 |
5 | export default class ArticleLabel extends React.Component {
6 | render() {
7 | const style = {
8 | backgroundColor: this.props.color,
9 | };
10 | return (
11 |
12 | {this.props.name}
13 |
14 | );
15 | }
16 | }
17 |
18 | ArticleLabel.defaultProps = {
19 | type: 'category',
20 | name: '前端',
21 | color: 'blue',
22 | };
23 |
24 | ArticleLabel.propTypes = {
25 | type: PropTypes.string,
26 | name: PropTypes.string,
27 | color: PropTypes.string,
28 | };
29 |
--------------------------------------------------------------------------------
/src/js/components/blog/rightsider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link } from 'react-router-dom';
4 | import CategoryCard from './categorycard';
5 | import TagCard from './tagcard';
6 |
7 | const ArchiveCard = () => (
8 |
9 | 所有文章
10 |
11 | );
12 |
13 | export default class RightSider extends React.Component {
14 | render() {
15 | return (
16 |
21 | );
22 | }
23 | }
24 |
25 | RightSider.defaultProps = {
26 | issues: [],
27 | };
28 |
29 | RightSider.propTypes = {
30 | issues: PropTypes.array,
31 | };
32 |
--------------------------------------------------------------------------------
/src/js/components/tag/articlelistitem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link } from 'react-router-dom';
4 |
5 | export default class ArticleListItem extends React.Component {
6 | render() {
7 | const tag = this.props.tag;
8 | const titleList = this.props.items.map(item => {item.updated_at.split('T')[0]}{item.title});
9 | return (
10 |
16 | );
17 | }
18 | }
19 |
20 | ArticleListItem.defaultProps = {
21 | tag: 'tag',
22 | items: null,
23 | };
24 |
25 | ArticleListItem.propTypes = {
26 | tag: PropTypes.string,
27 | items: PropTypes.array,
28 | };
29 |
--------------------------------------------------------------------------------
/src/js/components/archive/archiveitemlist.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link } from 'react-router-dom';
4 |
5 | export default class ArchiveItemList extends React.Component {
6 | render() {
7 | const year = this.props.year;
8 | const titleList = this.props.items.map(item => {item.updated_at.split('T')[0]}{item.title});
9 | return (
10 |
16 | );
17 | }
18 | }
19 |
20 | ArchiveItemList.defaultProps = {
21 | year: 'year',
22 | items: null,
23 | };
24 |
25 | ArchiveItemList.propTypes = {
26 | year: PropTypes.string,
27 | items: PropTypes.array,
28 | };
29 |
--------------------------------------------------------------------------------
/src/js/components/category/articlelistitem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link } from 'react-router-dom';
4 |
5 | export default class ArticleListItem extends React.Component {
6 | render() {
7 | const category = this.props.category;
8 | const titleList = this.props.items.map(item => {item.updated_at.split('T')[0]}{item.title});
9 | return (
10 |
11 |
{category}
12 |
15 |
16 | );
17 | }
18 | }
19 |
20 | ArticleListItem.defaultProps = {
21 | category: 'category',
22 | items: null,
23 | };
24 |
25 | ArticleListItem.propTypes = {
26 | category: PropTypes.string,
27 | items: PropTypes.array,
28 | };
29 |
--------------------------------------------------------------------------------
/src/js/components/home/homeheader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import '../../../css/home/homeheader.css';
4 |
5 | export default class HomeHeader extends React.Component {
6 | render() {
7 | return (
8 |
9 |
薛彬的个人主页
10 |
Xue bin Personal Growth Website
11 |
12 |
Home
13 |
Blog
14 |
Demo
15 |
About Me
16 |
17 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/js/components/blog/articlepaging.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Pagination } from 'antd';
4 |
5 | export default class ArticlePaging extends React.Component {
6 | constructor() {
7 | super();
8 | this.onChangePage = this.onChangePage.bind(this);
9 | }
10 |
11 | onChangePage(pageNumber) {
12 | this.props.handlePageChange(pageNumber);
13 | }
14 |
15 | render() {
16 | return (
17 |
20 | );
21 | }
22 | }
23 |
24 | ArticlePaging.defaultProps = {
25 | handlePageChange: null,
26 | defaultPageSize: 5,
27 | total: 10,
28 | };
29 |
30 | ArticlePaging.propTypes = {
31 | handlePageChange: PropTypes.func,
32 | defaultPageSize: PropTypes.number,
33 | total: PropTypes.number,
34 | };
35 |
--------------------------------------------------------------------------------
/src/js/containers/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { HashRouter, Route } from 'react-router-dom';
3 | import HomeHeader from '../components/home/homeheader';
4 | import HomeFooter from '../components/home/homefooter';
5 | import Home from '../containers/home';
6 | import Blog from '../containers/blog';
7 | import Demo from '../containers/demo';
8 | import About from '../containers/about';
9 |
10 | export default class Index extends React.Component {
11 | render() {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/js/components/home/homecard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import PropTypes from 'prop-types';
4 |
5 | export default class HomeCard extends React.Component {
6 | render() {
7 | const contentList = this.props.cardContent.map(item => {item.title});
8 | return (
9 |
10 |
11 | {this.props.cardName}
12 |
13 |
18 |
19 | );
20 | }
21 | }
22 |
23 | HomeCard.defaultProps = {
24 | cardName: 'Card Name',
25 | cardUrl: 'Card Url',
26 | cardContent: [],
27 | };
28 |
29 | HomeCard.propTypes = {
30 | cardName: PropTypes.string,
31 | cardUrl: PropTypes.string,
32 | cardContent: PropTypes.array,
33 | };
34 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path = require('path');
3 |
4 | // config
5 | module.exports = {
6 | context: __dirname + '/src',
7 | entry: "./js/app.js",
8 | module: {
9 | loaders: [
10 | {
11 | test: /\.js?$/,
12 | exclude: /(node_modules)/,
13 | loader: 'babel-loader',
14 | query: {
15 | presets: ['react', 'es2015']
16 | }
17 | }, {
18 | test: /\.css$/,
19 | loader: 'style-loader!css-loader'
20 | }, {
21 | test: /\.js$/,
22 | exclude: /(node_modules)/,
23 | loader: 'eslint-loader'
24 | }, {
25 | test: /\.json$/,
26 | loader: 'json-loader'
27 | }
28 | ]
29 | },
30 | output: {
31 | path: path.resolve(__dirname, './dist'),
32 | filename: 'js/[name].js',
33 | publicPath: './dist/',
34 | chunkFilename: 'js/async/[name].js'
35 | // path: __dirname + "/src/",
36 | // filename: "bundle.js"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/js/components/demo/democard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class DemoCard extends React.Component {
5 | render() {
6 | return (
7 |
8 |
{this.props.name}
9 |
10 |

11 |
12 |
16 |
17 | );
18 | }
19 | }
20 |
21 | DemoCard.defaultProps = {
22 | name: 'name',
23 | img: 'img',
24 | demo: 'demo',
25 | src: 'src',
26 | };
27 |
28 | DemoCard.propTypes = {
29 | name: PropTypes.string,
30 | img: PropTypes.string,
31 | demo: PropTypes.string,
32 | src: PropTypes.string,
33 | };
34 |
--------------------------------------------------------------------------------
/src/js/actions/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { REQUEST_ISSUES, RECEIVE_ISSUES } from '../constants/actiontype';
3 |
4 | const requestIssues = () => ({ type: REQUEST_ISSUES });
5 |
6 | const receiveIssues = json => ({ type: RECEIVE_ISSUES, posts: json });
7 |
8 | function fetchIssues() {
9 | return (dispatch) => {
10 | dispatch(requestIssues());
11 | return axios.get('https://api.github.com/repos/axuebin/articles/issues', {
12 | params: {
13 | creator: 'axuebin',
14 | labels: 'blog',
15 | },
16 | }).then(response => dispatch(receiveIssues(response.data))).catch(e => console.log(e));
17 | };
18 | }
19 |
20 | function shouldFetchIssues(state) {
21 | if (!state) {
22 | return true;
23 | }
24 | return !state.items.length;
25 | }
26 |
27 | export function fetchIssuesIfNeeded() {
28 | return (dispatch, getState) => {
29 | if (shouldFetchIssues(getState())) {
30 | return dispatch(fetchIssues());
31 | }
32 | return Promise.resolve();
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/js/containers/demo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DemoCard from '../components/demo/democard';
3 | import '../../css/demo/demo.css';
4 |
5 | export default class Blog extends React.Component {
6 | componentDidMount() {
7 | this.node.scrollIntoView();
8 | }
9 | render() {
10 | return (
11 | this.node = node} >
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 axuebin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/css/demo/demo.css:
--------------------------------------------------------------------------------
1 | .demo-container {
2 | display: flex;
3 | flex-wrap: wrap;
4 | justify-content: center;
5 | }
6 |
7 | .demo-democard {
8 | width: 24%;
9 | max-width: 400px;
10 | max-height: 400px;
11 | margin: 5px;
12 | background-color: rgb(255, 255, 255);
13 | }
14 |
15 | .demo-democard-title {
16 | height: 50px;
17 | line-height: 50px;
18 | font-size: 1.2em;
19 | color: rgb(0, 0, 0);
20 | text-align: center;
21 | border-bottom: 1px solid rgba(0, 0, 0, 0.65);
22 | }
23 |
24 | .demo-democard-title a {
25 | text-decoration: none;
26 | color: rgb(0, 0, 0);
27 | }
28 |
29 | .demo-democard-img {
30 | height: 300px;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | border-bottom: 1px solid rgba(0, 0, 0, 0.65);
35 | }
36 |
37 | .demo-democard-img img {
38 | max-width: 100%;
39 | max-height: 100%;
40 | }
41 |
42 | .demo-democard-desc {
43 | height: 50px;
44 | display: flex;
45 | line-height: 50px;
46 | font-size: 1.2em;
47 | }
48 |
49 | .demo-democard-desc a {
50 | flex-grow: 1;
51 | text-align: center;
52 | text-decoration: none;
53 | color: rgb(0, 0, 0);
54 | }
55 |
--------------------------------------------------------------------------------
/src/css/home/homeheader.css:
--------------------------------------------------------------------------------
1 | .home-header {
2 | width: 100%;
3 | height: 60px;
4 | display: flex;
5 | justify-content: flex-end;
6 | background-color: rgb(0, 0, 0);
7 | line-height: 60px;
8 | }
9 |
10 | .home-header-title {
11 | flex-grow: 1;
12 | display: flex;
13 | justify-content: center;
14 | font-size: 20px;
15 | }
16 |
17 | .home-header-title a {
18 | text-decoration: none;
19 | color: rgb(255, 255, 255);
20 | }
21 |
22 | .home-header-subtitle {
23 | flex-grow: 1;
24 | font-size: 16px;
25 | }
26 |
27 | .home-header-subtitle p {
28 | color: rgb(157, 157, 157);
29 | text-decoration: none;
30 | }
31 |
32 | .home-header-nav {
33 | flex-grow: 2;
34 | display: flex;
35 | justify-content: center;
36 | }
37 |
38 | .home-header-nav-item {
39 | flex-grow: 1;
40 | text-align: center;
41 | font-size: 16px;
42 | }
43 |
44 | .home-header-nav-item a {
45 | display: block;
46 | color: rgb(157, 157, 157);
47 | text-decoration: none;
48 | }
49 |
50 | .home-header-subtitle a:hover, .home-header-nav-item:hover a {
51 | color: rgb(255, 255, 255);
52 | }
53 |
54 | .home-header-nav-item:hover {
55 | background-color: rgb(50, 50, 50);
56 | border-bottom: 4px solid #13daec;
57 | }
58 |
--------------------------------------------------------------------------------
/src/css/home/homebanner.css:
--------------------------------------------------------------------------------
1 | .home-banner {
2 | width: 100%;
3 | height: 100%;
4 | background-color: rgb(50, 50, 50);
5 | display: flex;
6 | align-items: center;
7 | }
8 |
9 | .home-banner-me {
10 | width: 400px;
11 | height: 300px;
12 | margin: 0 auto;
13 | }
14 |
15 | .home-banner-photo {
16 | width: 400px;
17 | height: 60%;
18 | text-align: center;
19 | }
20 |
21 | .home-banner-photo img {
22 | width: 200px;
23 | height: 100%;
24 | border-radius: 50%;
25 | }
26 |
27 | .home-banner-desc {
28 | height: 60%;
29 | margin-top: 10px;
30 | text-align: center;
31 | color: rgb(255, 255, 255);
32 | }
33 |
34 | .home-banner-desc h1 {
35 | font-size: 30px;
36 | color: rgb(255, 255, 255);
37 | }
38 |
39 | .home-banner-desc p {
40 | font-size: 18px;
41 | }
42 |
43 | .home-banner-link {
44 | font-size: 18px;
45 | text-align: center;
46 | }
47 |
48 | .home-banner-link div {
49 | display: inline;
50 | }
51 |
52 | .home-banner-link .link:after {
53 | margin-right: 2px;
54 | margin-left: 2px;
55 | content: '/';
56 | }
57 |
58 | .home-banner-link a {
59 | color: rgb(19, 218, 236);
60 | text-decoration: none;
61 | }
62 |
63 | .home-banner-link a:hover {
64 | color: rgb(113, 233, 244);
65 | }
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-blog",
3 | "version": "1.0.0",
4 | "description": "React blog by axuebin",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "antd": "^2.12.8",
13 | "axios": "^0.19.0",
14 | "babel-loader": "^7.0.0",
15 | "babel-preset-es2015": "^6.24.1",
16 | "babel-preset-react": "^6.24.1",
17 | "babelify": "^7.3.0",
18 | "css-loader": "^0.28.4",
19 | "highlight.js": "^9.12.0",
20 | "marked": "^0.3.6",
21 | "prop-types": "^15.5.10",
22 | "react": "^15.6.1",
23 | "react-dom": "^15.6.1",
24 | "react-markdown": "^2.5.0",
25 | "react-redux": "^5.0.6",
26 | "react-router-dom": "^4.2.2",
27 | "redux": "^3.7.2",
28 | "redux-thunk": "^2.2.0",
29 | "style-loader": "^0.18.2",
30 | "webpack": "^2.6.1",
31 | "webpack-dev-server": "^2.4.5"
32 | },
33 | "devDependencies": {
34 | "eslint": "^4.5.0",
35 | "eslint-config-airbnb": "^15.1.0",
36 | "eslint-loader": "^1.9.0",
37 | "eslint-plugin-import": "^2.7.0",
38 | "eslint-plugin-jsx-a11y": "^6.0.2",
39 | "eslint-plugin-react": "^7.3.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/js/components/home/homebanner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../../../css/home/homebanner.css';
3 |
4 | export default class HomeBanner extends React.Component {
5 | render() {
6 | return (
7 |
8 |
9 |
10 |

11 |
12 |
13 |
薛彬
14 |
一个正在努力的程序猿及摄影爱好者
15 |
分享编码和摄影
16 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/js/components/blog/articlelist.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ArticleItem from './articleitem';
4 |
5 | export default class ArticleList extends React.Component {
6 | render() {
7 | const articlelist = [];
8 | const issues = this.props.issues;
9 | const currentPage = this.props.pageNumber;
10 | const defaultPageSize = this.props.defaultPageSize;
11 | const start = currentPage === 1 ? 0 : (currentPage - 1) * defaultPageSize;
12 | const end = start + defaultPageSize < issues.length ? start + defaultPageSize : issues.length;
13 | for (let i = start; i < end; i += 1) {
14 | const item = issues[i];
15 | console.log(item);
16 | articlelist.push();
17 | }
18 | return (
19 |
20 | {articlelist}
21 |
22 | );
23 | }
24 | }
25 |
26 | ArticleList.defaultProps = {
27 | issues: null,
28 | pageNumber: 1,
29 | defaultPageSize: 1,
30 | };
31 |
32 | ArticleList.propTypes = {
33 | issues: PropTypes.array,
34 | pageNumber: PropTypes.number,
35 | defaultPageSize: PropTypes.number,
36 | };
37 |
--------------------------------------------------------------------------------
/src/js/containers/blog.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Route } from 'react-router-dom';
4 | import BlogIndex from '../components/blog/blogindex';
5 | import Article from '../components/article/article';
6 | import Archive from '../components/archive/archive';
7 | import Tag from '../components/tag/tag';
8 | import Category from '../components/category/category';
9 |
10 | export default class Blog extends React.Component {
11 | componentDidMount() {
12 | this.node.scrollIntoView();
13 | }
14 |
15 | render() {
16 | return (
17 | this.node = node} >
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | Blog.defaultProps = {
29 | match: null,
30 | url: 'url',
31 | };
32 |
33 | Blog.propTypes = {
34 | match: PropTypes.object,
35 | url: PropTypes.string,
36 | };
37 |
--------------------------------------------------------------------------------
/src/css/blog/tagcard.css:
--------------------------------------------------------------------------------
1 | .blog-rightsider-archive {
2 | width: 100%;
3 | height: 50px;
4 | line-height: 50px;
5 | margin-bottom: 10px;
6 | text-align: center;
7 | background-color: rgb(255, 255, 255);
8 | transition: transform 0.25s ease;
9 | }
10 |
11 | .blog-rightsider-archive a {
12 | display: block;
13 | font-size: 20px;
14 | text-decoration: none;
15 | color: rgb(0, 0, 0);
16 | }
17 |
18 | .blog-rightsider-archive:hover {
19 | transform: scale(1.1);
20 | -ms-transform: scale(1.1);
21 | -webkit-transform: scale(1.1);
22 | -moz-transform: scale(1.1);
23 | }
24 |
25 | .blog-tagcard-title {
26 | height: 50px;
27 | line-height: 50px;
28 | text-align: center;
29 | font-size: 20px;
30 | color: rgb(0, 0, 0);
31 | border-bottom: 1px solid rgba(0, 0, 0, 0.65);
32 | }
33 |
34 | .blog-tagcard-content {
35 | padding: 20px;
36 | }
37 |
38 | .blog-tagcard-content a {
39 | display: inline-block;
40 | padding: 0px 10px;
41 | margin: 5px 5px;
42 | font-size: 16px;
43 | line-height: 30px;
44 | border: 1px solid rgba(0, 0, 0, 0.35);
45 | border-radius: 5px;
46 | color: rgba(0, 0, 0, 0.65);
47 | text-decoration: none;
48 | }
49 |
50 | .blog-tagcard-content a:hover {
51 | background-color: rgba(0, 0, 0, 0.3);
52 | color: rgb(255, 255, 255);
53 | }
54 |
--------------------------------------------------------------------------------
/src/js/components/blog/blogindex.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Row, Col } from 'antd';
4 | import { connect } from 'react-redux';
5 | import { fetchIssuesIfNeeded } from '../../actions/index';
6 | import ArticleArea from './articlearea';
7 | import RightSider from './rightsider';
8 |
9 | class BlogIndex extends React.Component {
10 | componentDidMount() {
11 | const { dispatch } = this.props;
12 | dispatch(fetchIssuesIfNeeded());
13 | }
14 | render() {
15 | if (this.props.isFetching) {
16 | return null;
17 | }
18 | return (
19 |
27 | );
28 | }
29 | }
30 |
31 | BlogIndex.defaultProps = {
32 | dispatch: null,
33 | isFetching: true,
34 | items: [],
35 | };
36 |
37 | BlogIndex.propTypes = {
38 | dispatch: PropTypes.func,
39 | isFetching: PropTypes.bool,
40 | items: PropTypes.array,
41 | };
42 |
43 | function mapStateToProps(state) {
44 | const { isFetching, items } = state || { isFetching: true, items: [] };
45 | return { isFetching, items };
46 | }
47 |
48 | export default connect(mapStateToProps)(BlogIndex);
49 |
--------------------------------------------------------------------------------
/src/js/components/blog/articlearea.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ArticleList from './articlelist';
4 | import ArticlePaging from './articlepaging';
5 | import '../../../css/blog/articlearea.css';
6 |
7 | const ArticleBanner = () => (
8 |
9 |
欢迎来到我的博客,请指教 ~
10 |
找工作呀找工作呀 ~
11 |
12 | );
13 |
14 | export default class ArticleArea extends React.Component {
15 | constructor() {
16 | super();
17 | this.state = {
18 | currentPage: 1,
19 | defaultPageSize: 8,
20 | };
21 | this.handlePageChange = this.handlePageChange.bind(this);
22 | }
23 |
24 | handlePageChange(pageNumber) {
25 | this.setState({ currentPage: pageNumber });
26 | }
27 |
28 | render() {
29 | if (this.props.issues.length === 0) {
30 | return null;
31 | }
32 | return (
33 |
38 | );
39 | }
40 | }
41 |
42 | ArticleArea.defaultProps = {
43 | issues: null,
44 | };
45 |
46 | ArticleArea.propTypes = {
47 | issues: PropTypes.array,
48 | };
49 |
--------------------------------------------------------------------------------
/src/js/components/tag/tag.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Row, Col } from 'antd';
4 | import { connect } from 'react-redux';
5 | import { fetchIssuesIfNeeded } from '../../actions/index';
6 | import ArticleList from './articlelist';
7 | import TagCard from './tagcard';
8 | import '../../../css/archive/archive.css';
9 |
10 | class Tag extends React.Component {
11 | componentDidMount() {
12 | const { dispatch } = this.props;
13 | dispatch(fetchIssuesIfNeeded());
14 | }
15 | render() {
16 | if (this.props.isFetching) {
17 | return null;
18 | }
19 | return (
20 |
28 | );
29 | }
30 | }
31 |
32 | Tag.defaultProps = {
33 | dispatch: null,
34 | isFetching: true,
35 | items: [],
36 | };
37 |
38 | Tag.propTypes = {
39 | dispatch: PropTypes.func,
40 | isFetching: PropTypes.bool,
41 | items: PropTypes.array,
42 | };
43 |
44 | function mapStateToProps(state) {
45 | const { isFetching, items } = state || { isFetching: true, items: [] };
46 | return { isFetching, items };
47 | }
48 |
49 | export default connect(mapStateToProps)(Tag);
50 |
--------------------------------------------------------------------------------
/src/js/components/archive/archive.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Row, Col } from 'antd';
4 | import { connect } from 'react-redux';
5 | import { fetchIssuesIfNeeded } from '../../actions/index';
6 | import ArchiveList from './archivelist';
7 | import YearCard from './yearcard';
8 | import '../../../css/archive/archive.css';
9 |
10 | class Archive extends React.Component {
11 | componentDidMount() {
12 | const { dispatch } = this.props;
13 | dispatch(fetchIssuesIfNeeded());
14 | }
15 | render() {
16 | if (this.props.isFetching) {
17 | return null;
18 | }
19 | return (
20 |
28 | );
29 | }
30 | }
31 |
32 | Archive.defaultProps = {
33 | dispatch: null,
34 | isFetching: true,
35 | items: [],
36 | };
37 |
38 | Archive.propTypes = {
39 | dispatch: PropTypes.func,
40 | isFetching: PropTypes.bool,
41 | items: PropTypes.array,
42 | };
43 |
44 | function mapStateToProps(state) {
45 | const { isFetching, items } = state || { isFetching: true, items: [] };
46 | return { isFetching, items };
47 | }
48 |
49 | export default connect(mapStateToProps)(Archive);
50 |
--------------------------------------------------------------------------------
/src/js/components/category/category.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Row, Col } from 'antd';
4 | import { connect } from 'react-redux';
5 | import { fetchIssuesIfNeeded } from '../../actions/index';
6 | import ArticleList from './articlelist';
7 | import CategoryCard from './categorycard';
8 | import '../../../css/archive/archive.css';
9 |
10 | class Category extends React.Component {
11 | componentDidMount() {
12 | const { dispatch } = this.props;
13 | dispatch(fetchIssuesIfNeeded());
14 | }
15 | render() {
16 | if (this.props.isFetching) {
17 | return null;
18 | }
19 | return (
20 |
28 | );
29 | }
30 | }
31 |
32 | Category.defaultProps = {
33 | dispatch: null,
34 | isFetching: true,
35 | items: [],
36 | };
37 |
38 | Category.propTypes = {
39 | dispatch: PropTypes.func,
40 | isFetching: PropTypes.bool,
41 | items: PropTypes.array,
42 | };
43 |
44 | function mapStateToProps(state) {
45 | const { isFetching, items } = state || { isFetching: true, items: [] };
46 | return { isFetching, items };
47 | }
48 |
49 | export default connect(mapStateToProps)(Category);
50 |
--------------------------------------------------------------------------------
/src/css/layout/layout.css:
--------------------------------------------------------------------------------
1 | .main {
2 | width: 100%;
3 | display: flex;
4 | justify-content: center;
5 | background-color: rgb(50, 50, 50);
6 | }
7 |
8 | .main-container {
9 | width: 90%;
10 | height: auto;
11 | min-height: 100%;
12 | margin: 50px 0px;
13 | }
14 |
15 | .archive-list-area {
16 | width: 100%;
17 | display: flex;
18 | flex-wrap: wrap;
19 | justify-content: flex-start;
20 | }
21 |
22 | .archive-list {
23 | width: 98%;
24 | background-color: rgb(255, 255, 255);
25 | }
26 |
27 | .rightsider {
28 | width: 100%;
29 | display: flex;
30 | flex-wrap: wrap;
31 | justify-content: flex-end;
32 | }
33 |
34 | .rightsider-card {
35 | width: 100%;
36 | min-height: 500px;
37 | margin-bottom: 10px;
38 | background-color: rgb(255, 255, 255);
39 | }
40 |
41 | .rightsider-card-title {
42 | height: 50px;
43 | line-height: 50px;
44 | text-align: center;
45 | font-size: 20px;
46 | color: rgb(0, 0, 0);
47 | border-bottom: 1px solid rgba(0, 0, 0, 0.65);
48 | }
49 |
50 | .rightsider-card-content li {
51 | height: 40px;
52 | list-style: none;
53 | line-height: 40px;
54 | padding: 0px 20px;
55 | font-size: 18px;
56 | }
57 |
58 | .rightsider-card-content li:hover {
59 | background-color: rgba(0, 0, 0, 0.3);
60 | color: rgba(255, 255, 255, 1);
61 | }
62 |
63 | .rightsider-card-content a {
64 | color: rgba(0, 0, 0, 0.65);
65 | text-decoration: none;
66 | }
67 |
68 | .rightsider-card-content span {
69 | color: inherit;
70 | float: right;
71 | }
72 |
--------------------------------------------------------------------------------
/src/js/components/archive/archivelist.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ArchiveItemList from './archiveitemlist';
4 |
5 | export default class ArchiveList extends React.Component {
6 | constructor() {
7 | super();
8 | this.getYearData = this.getYearData.bind(this);
9 | }
10 | getYearData(issues) {
11 | const len = issues.length;
12 | let year = 0;
13 | const articles = {};
14 | for (let i = 0; i < len; i += 1) {
15 | const time = parseInt(issues[i].updated_at.substring(0, 4), 10);
16 | if (time !== year) {
17 | if (!articles[time]) {
18 | articles[time] = [];
19 | }
20 | year = time;
21 | }
22 | articles[time].push(issues[i]);
23 | }
24 | return articles;
25 | }
26 | render() {
27 | const articles = this.getYearData(this.props.issues);
28 | const articleList = [];
29 | Object.keys(articles).forEach((key) => {
30 | if (Object.prototype.toString.call(articles[key]) === '[object Array]') {
31 | articleList.push();
32 | }
33 | });
34 | return (
35 |
36 |
37 | {articleList}
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | ArchiveList.defaultProps = {
45 | issues: null,
46 | };
47 |
48 | ArchiveList.propTypes = {
49 | issues: PropTypes.array,
50 | };
51 |
--------------------------------------------------------------------------------
/src/js/components/archive/yearcard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Icon } from 'antd';
4 |
5 | export default class YearCard extends React.Component {
6 | constructor() {
7 | super();
8 | this.getYearSum = this.getYearSum.bind(this);
9 | }
10 | getYearSum(issues) {
11 | const yearList = [];
12 | const yearHash = {};
13 | for (let i = 0; i < issues.length; i += 1) {
14 | const year = parseInt(issues[i].updated_at.substring(0, 4), 10);
15 | if (yearHash[year] === undefined) {
16 | yearHash[year] = true;
17 | const yearTemp = { year, sum: 1 };
18 | yearList.push(yearTemp);
19 | } else {
20 | for (let j = 0; j < yearList.length; j += 1) {
21 | if (yearList[j].year === year) {
22 | yearList[j].sum += 1;
23 | }
24 | }
25 | }
26 | }
27 | return yearList;
28 | }
29 | render() {
30 | const yearList = this.getYearSum(this.props.issues).map(item => {item.year}{item.sum});
31 | return (
32 |
33 |
34 |
统计
35 |
36 | {yearList}
37 |
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | YearCard.defaultProps = {
45 | issues: null,
46 | };
47 |
48 | YearCard.propTypes = {
49 | issues: PropTypes.array,
50 | };
51 |
--------------------------------------------------------------------------------
/src/js/components/article/article.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Row, Col } from 'antd';
3 | import PropTypes from 'prop-types';
4 | import axios from 'axios';
5 | import ArticleContent from './content';
6 | import ArticleNavigation from './navigation';
7 | import '../../../css/article/article.css';
8 |
9 | export default class Article extends React.Component {
10 | constructor() {
11 | super();
12 | this.state = {
13 | articleContent: null,
14 | };
15 | }
16 | componentDidMount() {
17 | this.node.scrollIntoView();
18 | const url = `https://api.github.com/repos/axuebin/articles/issues/${this.props.match.params.number}`;
19 | axios.get(url).then((response) => {
20 | const data = response.data;
21 | const articleContent = ;
22 | const articleNavigation = ;
23 | this.setState({ articleContent, articleNavigation });
24 | }).catch(e => console.log(e));
25 | }
26 | render() {
27 | return (
28 | this.node = node} >
29 |
30 |
31 | {this.state.articleContent}
32 | {this.state.articleNavigation}
33 |
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 | Article.defaultProps = {
41 | match: 'match',
42 | };
43 |
44 | Article.propTypes = {
45 | match: PropTypes.object,
46 | };
47 |
--------------------------------------------------------------------------------
/src/css/home/homecard.css:
--------------------------------------------------------------------------------
1 | .home-card-area {
2 | width: 100%;
3 | height: 500px;
4 | background-color: grey;
5 | display: flex;
6 | align-items: center;
7 | justify-content: space-around;
8 | }
9 |
10 | .home-card {
11 | width: 400px;
12 | height: 80%;
13 | max-height: 400px;
14 | border-radius: 100px;
15 | /*transition: transform 0.25s ease;*/
16 | }
17 |
18 |
19 | /* 卡片放大效果 */
20 |
21 |
22 | /*.home-card:hover {
23 | transform: scale(1.1);
24 | -ms-transform: scale(1.1);
25 | -webkit-transform: scale(1.1);
26 | -moz-transform: scale(1.1);
27 | }*/
28 |
29 | .home-card-name {
30 | width: 100%;
31 | height: 20%;
32 | padding: 0;
33 | background-color: white;
34 | font-size: 20px;
35 | display: flex;
36 | justify-content: center;
37 | align-items: center;
38 | transition: transform 0.25s ease;
39 | }
40 |
41 | .home-card-name:hover {
42 | transform: scale(1.1);
43 | -ms-transform: scale(1.1);
44 | -webkit-transform: scale(1.1);
45 | -moz-transform: scale(1.1);
46 | }
47 |
48 | .home-card-name a {
49 | display: block;
50 | color: rgb(51, 51, 51);
51 | }
52 |
53 | .home-card-content {
54 | width: 100%;
55 | height: 80%;
56 | display: flex;
57 | justify-content: center;
58 | align-items: center;
59 | font-size: 16px;
60 | color: rgb(255, 255, 255);
61 | background-color: black;
62 | }
63 |
64 | .home-card-content ul {
65 | text-align: center;
66 | }
67 |
68 | .home-card-content li {
69 | margin-top: 5px;
70 | }
71 |
72 | .home-card-content li a {
73 | text-decoration: none;
74 | color: rgb(255, 255, 255);
75 | }
76 |
77 | .home-card-content li a:hover {
78 | color: rgb(19, 218, 236);
79 | }
80 |
--------------------------------------------------------------------------------
/src/js/components/article/content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import marked from 'marked';
4 | import hljs from 'highlight.js';
5 | // import Comment from './comment';
6 |
7 |
8 | const Comment = props => (
9 |
12 | );
13 |
14 | export default class ArticleContent extends React.Component {
15 | componentWillMount() {
16 | marked.setOptions({
17 | highlight: code => hljs.highlightAuto(code).value,
18 | });
19 | }
20 |
21 | render() {
22 | const commentUrl = `https://github.com/axuebin/articles/issues/${this.props.number}`;
23 | return (
24 |
25 |
26 |
27 |
{this.props.title}
28 |
29 |
30 | {this.props.time.split('T')[0]}
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 | ArticleContent.defaultProps = {
41 | number: 'number',
42 | title: 'title',
43 | time: 'time',
44 | content: 'content',
45 | };
46 |
47 | ArticleContent.propTypes = {
48 | number: PropTypes.number,
49 | title: PropTypes.string,
50 | time: PropTypes.string,
51 | content: PropTypes.string,
52 | };
53 |
54 | Comment.defaultProps = {
55 | commentUrl: 'commentUrl',
56 | };
57 |
58 | Comment.propTypes = {
59 | commentUrl: PropTypes.string,
60 | };
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-Blog
2 |
3 | 学习React全家桶,计划写一个博客。
4 |
5 | 边学边写边思考。。。
6 |
7 |
8 | ### TodoList
9 |
10 | * [x] 静态页面
11 | * [x] 整理目录结构
12 | * [x] Github issus作文章数据源
13 | * [x] react-markdown渲染md
14 | * [x] 代码高亮
15 | * [x] 摘要
16 | * [x] 分类
17 | * [x] 标签
18 | * [x] 翻页功能
19 | * [x] 首页卡片
20 | * [x] Demo页面
21 | * [x] 评论功能(跳转到Github Issues页面。。)
22 | * [ ] 回到顶部
23 | * [ ] Redux管理数据
24 | * [ ] react-router路由
25 | * [ ] 整理代码
26 | * [ ] 渲染优化,打包优化
27 | * [ ] 适配移动端
28 | * [ ] React Native App
29 |
30 | ### Update Log
31 |
32 | |时间|事件|
33 | |:---|:---|
34 | |2017/09/20|1.实现文章内部目录 2.博客页移动端自动隐藏右侧|
35 | |2017/09/18|利用antd实现翻页功能|
36 | |2017/09/17|1.增加类别页 2.修改Demo页 3.整理CSS代码|
37 | |2017/09/11|增加归档页|
38 | |2017/09/09|增加标签列表|
39 | |2017/09/08|每个类别文章数|
40 | |2017/09/07|1.加入Redux作为Github Issues数据的管理 2.增加分类模块|
41 | |2017/09/06|整理项目目录结构|
42 | |2017/09/04|1.修改首页卡片数据源 2.修改文章摘要 3.使用`scrollIntoView()`使页面改变后回到顶部|
43 | |2017/09/03|1.增加文章页面 2.使用`marked`渲染Markdown 3.使用`highlight.js`高亮代码|
44 | |2017/09/02|使用Github Issues作为文章数据源|
45 | |2017/08/31|1.静态页面初步完成 2.使用本地json模拟数据源|
46 | |2017/08/29|React-router V4 作为项目路由|
47 | |2017/08/27|First Blood|
48 |
49 | ### 问题汇总
50 |
51 | - React-router的Link和Route需要嵌套在同一个Router底下?
52 | - 刷新页面出现`Cannot GET /`提示,路由未生效。
53 | - 由于刷新之后,会根据URL对服务器发送请求,而不是处理路由,导致出现`Cannot GET /`错误。
54 | - 通过修改``→``。
55 | - ``借助URL上的哈希值(hash)来实现路由。可以在不需要全屏刷新的情况下,达到切换页面的目的。
56 | - 二级路由只改变URL,页面不跳转
57 | - react-markdown不支持表格的渲染
58 | - 路由跳转后页面不会自动回到顶部
59 | - 使用`scrollIntoView()`,但是不能跳到整个页面的顶部。
60 | - Tag List显示字体大小应该如何计算。(或采用词云来展示?)
61 | - Expected 'this' to be used by class method。建议使用static,但是用了static之后bind会出错。。暂时关闭这个rule。。
62 | - 现在很多模块和css是重复的,需要整理代码。。
63 | - 文章内部目录的实现待优化,暂时也无法实现点击跳转
64 | - 首页渲染优化的问题
65 | - 本来想的在文章底部增加评论框,直接调用Github API创建评论和获取评论列表,但是发现需要登录授权,暂缓
66 |
--------------------------------------------------------------------------------
/src/js/components/category/articlelist.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ArticleListItem from './articlelistitem';
4 | import { COLOR_LABEL_CATEGORY } from '../../constants/config';
5 |
6 | export default class ArticleList extends React.Component {
7 | constructor() {
8 | super();
9 | this.getCategoryData = this.getCategoryData.bind(this);
10 | }
11 | getCategoryData(issues) {
12 | const len = issues.length;
13 | let name = '';
14 | const articles = {};
15 | for (let i = 0; i < len; i += 1) {
16 | const labels = issues[i].labels;
17 | for (let j = 0; j < labels.length; j += 1) {
18 | if (labels[j].color === COLOR_LABEL_CATEGORY) {
19 | const category = labels[j].name;
20 | if (name !== category) {
21 | if (!articles[category]) {
22 | articles[category] = [];
23 | }
24 | name = category;
25 | }
26 | articles[category].push(issues[i]);
27 | }
28 | }
29 | }
30 | return articles;
31 | }
32 | render() {
33 | if (!this.props.issues) {
34 | return null;
35 | }
36 | const articles = this.getCategoryData(this.props.issues);
37 | const articleList = [];
38 | Object.keys(articles).forEach((key) => {
39 | if (Object.prototype.toString.call(articles[key]) === '[object Array]') {
40 | articleList.push();
41 | }
42 | });
43 | return (
44 |
45 |
46 | {articleList}
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | ArticleList.defaultProps = {
54 | issues: null,
55 | };
56 |
57 | ArticleList.propTypes = {
58 | issues: PropTypes.array,
59 | };
60 |
--------------------------------------------------------------------------------
/src/js/components/tag/articlelist.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ArticleListItem from './articlelistitem';
4 | import { COLOR_BLOG_CATEGORY, COLOR_LABEL_CATEGORY } from '../../constants/config';
5 |
6 | export default class ArticleList extends React.Component {
7 | constructor() {
8 | super();
9 | this.getTagData = this.getTagData.bind(this);
10 | }
11 | getTagData(issues) {
12 | const len = issues.length;
13 | let name = '';
14 | const articles = {};
15 | for (let i = 0; i < len; i += 1) {
16 | const labels = issues[i].labels;
17 | for (let j = 0; j < labels.length; j += 1) {
18 | if (labels[j].color !== COLOR_BLOG_CATEGORY && labels[j].color !== COLOR_LABEL_CATEGORY) {
19 | const tag = labels[j].name;
20 | if (name !== tag) {
21 | if (!articles[tag]) {
22 | articles[tag] = [];
23 | }
24 | name = tag;
25 | }
26 | articles[tag].push(issues[i]);
27 | }
28 | }
29 | }
30 | return articles;
31 | }
32 | render() {
33 | if (!this.props.issues) {
34 | return null;
35 | }
36 | const articles = this.getTagData(this.props.issues);
37 | const articleList = [];
38 | Object.keys(articles).forEach((key) => {
39 | if (Object.prototype.toString.call(articles[key]) === '[object Array]') {
40 | articleList.push();
41 | }
42 | });
43 | return (
44 |
45 |
46 | {articleList}
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | ArticleList.defaultProps = {
54 | issues: null,
55 | };
56 |
57 | ArticleList.propTypes = {
58 | issues: PropTypes.array,
59 | };
60 |
--------------------------------------------------------------------------------
/src/js/components/blog/tagcard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Icon } from 'antd';
4 | import { Link } from 'react-router-dom';
5 | import { COLOR_BLOG_CATEGORY, COLOR_LABEL_CATEGORY } from '../../constants/config';
6 | import '../../../css/blog/tagcard.css';
7 |
8 | export default class TagCard extends React.Component {
9 | render() {
10 | const tagList = [];
11 | const tagHash = {};
12 | const issues = this.props.issues;
13 | // 类别去重计数(待优化)
14 | for (let i = 0; i < issues.length; i += 1) {
15 | for (let j = 0; j < issues[i].labels.length; j += 1) {
16 | const label = issues[i].labels[j];
17 | if (label.color !== COLOR_BLOG_CATEGORY && label.color !== COLOR_LABEL_CATEGORY) {
18 | const id = label.id;
19 | const name = label.name;
20 | if (tagHash[name] === undefined) {
21 | tagHash[name] = true;
22 | const tagTemp = { id, name, url: encodeURI(name), sum: 1 };
23 | tagList.push(tagTemp);
24 | } else {
25 | for (let k = 0; k < tagList.length; k += 1) {
26 | if (tagList[k].name === name) {
27 | tagList[k].sum += 1;
28 | }
29 | }
30 | }
31 | }
32 | }
33 | }
34 | const tagLinkList = tagList.sort((a, b) => b.sum - a.sum).map(item => {item.name} ({item.sum}));
35 | return (
36 |
37 |
标签
38 |
39 | {tagLinkList}
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | TagCard.defaultProps = {
47 | issues: [],
48 | };
49 |
50 | TagCard.propTypes = {
51 | issues: PropTypes.array,
52 | };
53 |
--------------------------------------------------------------------------------
/src/js/components/tag/tagcard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Icon } from 'antd';
4 | import { COLOR_BLOG_CATEGORY, COLOR_LABEL_CATEGORY } from '../../constants/config';
5 |
6 | export default class TagCard extends React.Component {
7 | constructor() {
8 | super();
9 | this.getTagSum = this.getTagSum.bind(this);
10 | }
11 | getTagSum(issues) {
12 | const tagList = [];
13 | const tagHash = {};
14 | for (let i = 0; i < issues.length; i += 1) {
15 | const labels = issues[i].labels;
16 | for (let j = 0; j < labels.length; j += 1) {
17 | if (labels[j].color !== COLOR_BLOG_CATEGORY && labels[j].color !== COLOR_LABEL_CATEGORY) {
18 | const tag = labels[j].name;
19 | if (tagHash[tag] === undefined) {
20 | tagHash[tag] = true;
21 | const tagTemp = { tag, sum: 1 };
22 | tagList.push(tagTemp);
23 | } else {
24 | for (let k = 0; k < tagList.length; k += 1) {
25 | if (tagList[k].tag === tag) {
26 | tagList[k].sum += 1;
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 | return tagList;
34 | }
35 | render() {
36 | const tagList = this.getTagSum(this.props.issues).sort((a, b) => b.sum - a.sum).map(
37 | item => {item.tag}{item.sum});
38 | return (
39 |
40 |
41 |
统计
42 |
43 | {tagList}
44 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | TagCard.defaultProps = {
52 | issues: null,
53 | };
54 |
55 | TagCard.propTypes = {
56 | issues: PropTypes.array,
57 | };
58 |
--------------------------------------------------------------------------------
/src/js/components/blog/categorycard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Icon } from 'antd';
4 | import { Link } from 'react-router-dom';
5 | import { COLOR_LABEL_CATEGORY } from '../../constants/config';
6 |
7 | export default class CategoryCard extends React.Component {
8 | render() {
9 | const categoryList = [];
10 | const categoryHash = {};
11 | const issues = this.props.issues;
12 | // 类别去重计数(待优化)
13 | for (let i = 0; i < issues.length; i += 1) {
14 | for (let j = 0; j < issues[i].labels.length; j += 1) {
15 | const label = issues[i].labels[j];
16 | if (label.color === COLOR_LABEL_CATEGORY) {
17 | const id = label.id;
18 | const name = label.name;
19 | if (categoryHash[name] === undefined) {
20 | categoryHash[name] = true;
21 | const categoryTemp = { id, name, url: encodeURI(name), sum: 1 };
22 | categoryList.push(categoryTemp);
23 | } else {
24 | for (let k = 0; k < categoryList.length; k += 1) {
25 | if (categoryList[k].name === name) {
26 | categoryList[k].sum += 1;
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
34 | const categoryLinkList = categoryList.sort((a, b) => b.sum - a.sum).map(
35 | item => {item.name}{item.sum});
36 |
37 | return (
38 |
39 |
分类
40 |
41 |
42 | {categoryLinkList}
43 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | CategoryCard.defaultProps = {
51 | issues: [],
52 | };
53 |
54 | CategoryCard.propTypes = {
55 | issues: PropTypes.array,
56 | };
57 |
--------------------------------------------------------------------------------
/src/js/components/category/categorycard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Icon } from 'antd';
4 | import { COLOR_LABEL_CATEGORY } from '../../constants/config';
5 |
6 | export default class CategoryCard extends React.Component {
7 | constructor() {
8 | super();
9 | this.getCategorySum = this.getCategorySum.bind(this);
10 | }
11 | getCategorySum(issues) {
12 | const categoryList = [];
13 | const categoryHash = {};
14 | for (let i = 0; i < issues.length; i += 1) {
15 | const labels = issues[i].labels;
16 | for (let j = 0; j < labels.length; j += 1) {
17 | if (labels[j].color === COLOR_LABEL_CATEGORY) {
18 | const category = labels[j].name;
19 | if (categoryHash[category] === undefined) {
20 | categoryHash[category] = true;
21 | const categoryTemp = { category, sum: 1 };
22 | categoryList.push(categoryTemp);
23 | } else {
24 | for (let k = 0; k < categoryList.length; k += 1) {
25 | if (categoryList[k].category === category) {
26 | categoryList[k].sum += 1;
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 | return categoryList;
34 | }
35 | render() {
36 | const categoryList = this.getCategorySum(this.props.issues).sort((a, b) => b.sum - a.sum).map(
37 | item => {item.category}{item.sum});
38 | return (
39 |
40 |
41 |
统计
42 |
43 | {categoryList}
44 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | CategoryCard.defaultProps = {
52 | issues: null,
53 | };
54 |
55 | CategoryCard.propTypes = {
56 | issues: PropTypes.array,
57 | };
58 |
--------------------------------------------------------------------------------
/src/js/components/blog/articleitem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Icon } from 'antd';
3 | import { Link } from 'react-router-dom';
4 | import PropTypes from 'prop-types';
5 | import { COLOR_BLOG_CATEGORY, COLOR_LABEL_CATEGORY } from '../../constants/config';
6 | import ArticleLabel from './articlelabel';
7 |
8 | export default class ArticleItem extends React.Component {
9 | render() {
10 | const category = [];
11 | const labelList = [];
12 | for (let i = 0; i < this.props.labels.length; i += 1) {
13 | const item = this.props.labels[i];
14 | if (item.color === COLOR_LABEL_CATEGORY) {
15 | category.push();
16 | } else if (item.color !== COLOR_BLOG_CATEGORY) {
17 | labelList.push();
18 | }
19 | }
20 | const time = this.props.time.split('T')[0];
21 | return (
22 |
23 |
24 | {this.props.title}
25 |
26 |
{time}
27 |
{category} {labelList}
28 |
29 |
30 | {this.props.desc.split('----')[0]}
31 | Learn more...
32 |
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | ArticleItem.defaultProps = {
40 | number: 'number',
41 | title: 'title',
42 | labels: 'label',
43 | time: 'time',
44 | desc: 'desc',
45 | };
46 |
47 | ArticleItem.propTypes = {
48 | number: PropTypes.number,
49 | title: PropTypes.string,
50 | labels: PropTypes.array,
51 | time: PropTypes.string,
52 | desc: PropTypes.string,
53 | };
54 |
--------------------------------------------------------------------------------
/src/js/containers/about.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../../css/about/about.css';
3 |
4 | export default class About extends React.Component {
5 | componentDidMount() {
6 | this.node.scrollIntoView();
7 | }
8 | render() {
9 | return (
10 | this.node = node} >
11 |
12 |
13 |
关于我
14 |
15 | - 一个正在摸爬滚打的野生程序猿, 有着强烈的学习欲望
16 | - 普通高校在读研究生,2018年毕业
17 | - 本科学的是电子信息工程专业,有点虚度光阴了,除了拿了几个奖似乎没什么收获
18 | - 因心底一直想学习编程相关的,研究生就选择了计算机应用技术专业,从零开始学习计算机
19 | - 嗯,现在暂时是那种不懂数据结构、操作系统以及算法的野生程序猿,我会努力弥补这些不足
20 | - 喜欢看书和实践,喜欢旅游和运动
21 |
22 |
23 |
24 |
33 |
34 |
41 |
42 |
48 |
49 |
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/js/components/home/homecardarea.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import axios from 'axios';
3 | import HomeCard from './homecard';
4 | import '../../../css/home/homecard.css';
5 |
6 | export default class HomeCardArea extends React.Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | articleCard: null,
11 | demoCard: null,
12 | aboutCard: null,
13 | };
14 | }
15 |
16 | componentDidMount() {
17 | axios.get('https://api.github.com/repos/axuebin/articles/issues', {
18 | params: {
19 | creator: 'axuebin',
20 | labels: 'blog',
21 | },
22 | }).then((response) => {
23 | const data = response.data;
24 | const articleList = [];
25 | for (let i = 0; i < 6; i += 1) {
26 | const articleTemp = {};
27 | articleTemp.title = data[i].title;
28 | articleTemp.id = data[i].number;
29 | articleTemp.url = `/blog/article/${data[i].number}`;
30 | articleList.push(articleTemp);
31 | }
32 |
33 | const demoList = [{
34 | id: 1,
35 | title: 'React实现的TodoList',
36 | url: '/demo',
37 | }, {
38 | id: 2,
39 | title: 'React实现的列车站点查询',
40 | url: '/demo',
41 | }, {
42 | id: 3,
43 | title: 'React实现的flex布局在线可视化',
44 | url: '/demo',
45 | }];
46 |
47 | const aboutList = [{
48 | id: 1,
49 | title: '2018届应届生',
50 | url: '/about',
51 | }, {
52 | id: 2,
53 | title: '求工作求工作求工作...',
54 | url: '/about',
55 | }];
56 |
57 | const articleCard = ;
58 | const demoCard = ;
59 | const aboutCard = ;
60 | this.setState({ articleCard, demoCard, aboutCard });
61 | }).catch(e => console.log(e));
62 | }
63 |
64 | render() {
65 | return (
66 |
67 | {this.state.articleCard}
68 | {this.state.demoCard}
69 | {this.state.aboutCard}
70 |
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/js/components/article/navigation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class Navigation extends React.Component {
5 | constructor() {
6 | super();
7 | this.state = {
8 | menuList: null,
9 | };
10 | this.getMenuList = this.getMenuList.bind(this);
11 | }
12 | // 写的有点丑 T T.
13 | getMenuList(content) {
14 | // const h2 = content.match(new RegExp('([^<]*)
','g')).map(item => getTitleText(item));
15 | const issues = content;
16 | const menu = [];
17 | const patt = /(#+)\s+?(.+)/g;
18 | let result = null;
19 | while ((result = patt.exec(issues))) {
20 | menu.push({ level: result[1].length, title: result[2] });
21 | }
22 | const menuObj = [];
23 | let level2Temp = null;
24 | let level3Temp = null;
25 | let level4Temp = null;
26 | for (let i = 0; i < menu.length; i += 1) {
27 | if (menu[i].level === 2) {
28 | level2Temp = {
29 | id: i,
30 | level: 2,
31 | title: menu[i].title,
32 | children: [],
33 | };
34 | menuObj.push(level2Temp);
35 | } else if (menu[i].level === 3) {
36 | level3Temp = {
37 | id: i,
38 | level: 3,
39 | title: menu[i].title,
40 | children: [],
41 | };
42 | level2Temp.children.push(level3Temp);
43 | } else if (menu[i].level === 4) {
44 | level4Temp = {
45 | id: i,
46 | level: 4,
47 | title: menu[i].title,
48 | };
49 | level3Temp.children.push(level4Temp);
50 | }
51 | }
52 | return menuObj.map(level2 => ({level2.title}
53 |
54 | {level2.children.map(level3 => (- {level3.title}
55 |
56 | {level3.children.map(level4 => (- {level4.title}
))}
57 |
58 | ))}
59 |
60 | ));
61 | }
62 |
63 | render() {
64 | const menuList = this.getMenuList(this.props.content);
65 | return (
66 |
76 | );
77 | }
78 | }
79 |
80 | Navigation.defaultProps = {
81 | content: 'content',
82 | };
83 |
84 | Navigation.propTypes = {
85 | content: PropTypes.string,
86 | };
87 |
--------------------------------------------------------------------------------
/src/css/blog/articlearea.css:
--------------------------------------------------------------------------------
1 | .blog-article-banner {
2 | width: 98%;
3 | height: 110px;
4 | border-bottom: 1px solid rgba(0, 0, 0, 0.65);
5 | background-color: rgb(255, 255, 255);
6 | }
7 |
8 | .blog-article-banner h2 {
9 | font-size: 16px;
10 | padding-left: 20px;
11 | line-height: 50px;
12 | }
13 |
14 | .blog-article-banner p {
15 | font-size: 14px;
16 | padding-left: 20px;
17 | line-height: 50px;
18 | }
19 |
20 | .blog-article-item {
21 | padding: 30px 20px;
22 | border-bottom: 1px solid rgba(0, 0, 0, 0.65);
23 | background-color: rgb(255, 255, 255);
24 | }
25 |
26 | .blog-article-item-title {
27 | height: 50px;
28 | font-size: 1.8em;
29 | }
30 |
31 | .blog-article-item-title a {
32 | text-decoration: none;
33 | color: rgb(0, 0, 0);
34 | }
35 |
36 | .blog-article-item-label {
37 | display: inline-block;
38 | height: 30px;
39 | font-size: 1.3em;
40 | }
41 |
42 | .blog-article-item-time {
43 | display: inline-block;
44 | height: 30px;
45 | font-size: 1.3em;
46 | }
47 |
48 | .blog-article-label {
49 | display: inline;
50 | margin: 0px 5px;
51 | }
52 |
53 | .blog-article-label a {
54 | padding: 8px;
55 | text-decoration: none;
56 | color: rgb(255, 255, 255);
57 | line-height: 2;
58 | }
59 |
60 | .blog-article-item-label i {
61 | margin-left: 5px;
62 | }
63 |
64 | .blog-article-item-desc {
65 | font-size: 1.3em;
66 | overflow: hidden;
67 | text-overflow: ellipsis;
68 | display: -webkit-box;
69 | -webkit-line-clamp: 1;
70 | -webkit-box-orient: vertical;
71 | }
72 |
73 | .blog-article-item-desc-more {
74 | float: right;
75 | }
76 |
77 | .blog-article-item-desc-more a {
78 | color: rgb(255, 255, 255);
79 | text-decoration: none;
80 | }
81 |
82 | .blog-article-paging {
83 | width: 98%;
84 | height: 50px;
85 | font-size: 1.3em;
86 | display: flex;
87 | justify-content: center;
88 | align-items: center;
89 | background-color: rgb(255, 255, 255);
90 | }
91 |
92 | .ant-pagination-item:hover {
93 | border-color: rgb(50, 50, 50);
94 | }
95 |
96 | .ant-pagination-item:hover a {
97 | color: rgb(50, 50, 50);
98 | }
99 |
100 | .ant-pagination-item-active {
101 | background-color: rgb(50, 50, 50);
102 | border-color: rgb(50, 50, 50);
103 | }
104 |
105 | .ant-pagination-item-active:focus, .ant-pagination-item-active:hover {
106 | background-color: rgb(50, 50, 50);
107 | border-color: rgb(50, 50, 50);
108 | }
109 |
110 | .ant-pagination-item-active:focus, .ant-pagination-item-active:hover a {
111 | color: rgb(255, 255, 255);
112 | }
113 |
114 | .ant-pagination-prev:hover, .ant-pagination-next:hover {
115 | color: rgb(50, 50, 50);
116 | border-color: rgb(50, 50, 50);
117 | }
118 |
--------------------------------------------------------------------------------
/src/css/article/article.css:
--------------------------------------------------------------------------------
1 | @import '~highlight.js/styles/atom-one-dark.css';
2 | .article-title {
3 | width: 100%;
4 | height: 100px;
5 | font-size: 2em;
6 | text-align: center;
7 | line-height: 100px;
8 | }
9 |
10 | .article-time {
11 | text-align: center;
12 | font-size: 1.2em;
13 | }
14 |
15 | .article-detail {
16 | padding: 10px 40px 20px 40px;
17 | line-height: 1.6;
18 | }
19 |
20 | .article-detail h2 {
21 | margin: 1.2em 0px;
22 | font-size: 1.8em;
23 | }
24 |
25 | .article-detail h3 {
26 | margin: 1.2em 0;
27 | font-size: 1.6em;
28 | }
29 |
30 | .article-detail h4 {
31 | margin: 1.2em 0;
32 | font-size: 1.4em;
33 | }
34 |
35 | .article-detail p {
36 | margin: 1.2em 0;
37 | font-size: 1.2em;
38 | }
39 |
40 | .article-detail ul, ol {
41 | margin: 1.2em 0;
42 | padding-left: 1.2em;
43 | }
44 |
45 | .article-detail li {
46 | font-size: 1em;
47 | list-style-type: disc;
48 | }
49 |
50 | .article-detail blockquote {
51 | padding: 1px 10px;
52 | border-left: 2px solid black;
53 | background: rgb(200, 200, 200);
54 | }
55 |
56 | .article-detail p code, .article-detail ul code {
57 | display: inline-block;
58 | margin: 0 2px;
59 | padding: 0 4px;
60 | color: rgb(29, 31, 33);
61 | border-radius: 3px;
62 | line-height: 20px;
63 | background: rgb(200, 200, 200);
64 | border: 1px solid #ddd;
65 | }
66 |
67 | .article-detail pre code {
68 | display: block;
69 | margin: 20px 0px;
70 | overflow-x: auto;
71 | padding: 0.5em;
72 | background: rgb(29, 31, 33);
73 | color: rgb(200, 200, 200);
74 | padding: 10px 20px;
75 | font-family: Consolas;
76 | font-size: 1.2em;
77 | line-height: 1.5;
78 | border-radius: 10px;
79 | }
80 |
81 | .article-detail table {
82 | width: 100%;
83 | border: 1px solid;
84 | font-size: 1.2em;
85 | border-collapse: collapse;
86 | }
87 |
88 | .article-detail p>img {
89 | max-width: 80%;
90 | display: block;
91 | margin: 0 auto;
92 | }
93 |
94 | .article-detail th, .article-detail td {
95 | height: 50px;
96 | padding: 0px 20px;
97 | text-align: center;
98 | vertical-align: middle;
99 | }
100 |
101 | .article-detail table>tbody>tr:nth-child(odd)>td, .article-detail table>tbody>tr:nth-child(odd)>th {
102 | background-color: #f9f9f9;
103 | }
104 |
105 | .article-navigation-content {
106 | padding-left: 20px;
107 | }
108 |
109 | .article-navigation-content ul {
110 | padding: 5px;
111 | }
112 |
113 | .article-navigation-content li {
114 | margin-left: 20px;
115 | list-style-type: disc;
116 | }
117 |
118 | .article-comment-box {
119 | width: 98%;
120 | height: 50px;
121 | font-size: 1.3em;
122 | border-top: 1px solid rgba(0, 0, 0, 0.65);
123 | display: flex;
124 | justify-content: center;
125 | align-items: center;
126 | background-color: rgb(255, 255, 255);
127 | }
128 |
129 | .article-comment-box a {
130 | text-decoration: none;
131 | color: rgb(0, 0, 0);
132 | }
133 |
--------------------------------------------------------------------------------