├── .browserslistrc ├── babel.config.js ├── public ├── favicon.png └── index.html ├── postcss.config.js ├── src ├── assets │ ├── img │ │ ├── avatar.png │ │ └── octocat.png │ ├── icons │ │ ├── svg │ │ │ ├── plus.svg │ │ │ ├── code.svg │ │ │ ├── octiconStar.svg │ │ │ ├── email.svg │ │ │ ├── repo.svg │ │ │ ├── octiconEye.svg │ │ │ ├── location.svg │ │ │ ├── slash.svg │ │ │ ├── link.svg │ │ │ ├── github.svg │ │ │ ├── share.svg │ │ │ ├── chat.svg │ │ │ ├── tag.svg │ │ │ ├── inbox.svg │ │ │ ├── eye.svg │ │ │ └── calendar.svg │ │ ├── index.js │ │ └── svgo.yml │ ├── style │ │ ├── layout.less │ │ └── github-markdown.css │ └── lib │ │ └── highlight.js ├── utils │ ├── index.js │ ├── documents.js │ ├── format.js │ └── services.js ├── views │ ├── About.vue │ ├── Archive.vue │ ├── Friend.vue │ ├── Mood.vue │ ├── Tag.vue │ ├── Category.vue │ ├── Home.vue │ └── Post.vue ├── components │ ├── SvgIcon.vue │ ├── MarkDown.vue │ ├── Footer.vue │ ├── Pagination.vue │ ├── ArchiveList.vue │ ├── Comment.vue │ ├── Sidebar.vue │ └── Header.vue ├── router.js ├── main.js ├── config.js ├── App.vue └── store.js ├── .prettierrc ├── .gitignore ├── .eslintrc.js ├── vue.config.js ├── package.json └── README.md /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/gitleaf/HEAD/public/favicon.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /src/assets/img/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/gitleaf/HEAD/src/assets/img/avatar.png -------------------------------------------------------------------------------- /src/assets/img/octocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/gitleaf/HEAD/src/assets/img/octocat.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "useTabs": false, 5 | "tabWidth": 2, 6 | "printWidth": 110, 7 | "bracketSpacing": true 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/icons/svg/plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/code.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/octiconStar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /src/assets/icons/svg/repo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon' // svg组件 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /src/assets/icons/svg/octiconEye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export const localSave = (key, value) => { 2 | localStorage.setItem(key, value) 3 | } 4 | 5 | export const localRead = (key, defaultValue = '') => { 6 | return localStorage.getItem(key) || defaultValue 7 | } 8 | 9 | export const getLocation = href => { 10 | const a = document.createElement('a') 11 | a.href = href 12 | return a 13 | } 14 | -------------------------------------------------------------------------------- /src/assets/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | # - name 8 | # 9 | # or: 10 | # - name: false 11 | # - name: true 12 | # 13 | # or: 14 | # - name: 15 | # param1: 1 16 | # param2: 2 17 | 18 | - removeAttrs: 19 | attrs: 20 | - 'fill' 21 | - 'fill-rule' 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "@vue/prettier"], 7 | rules: { 8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 10 | }, 11 | parserOptions: { 12 | parser: "babel-eslint" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/assets/icons/svg/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/slash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/style/layout.less: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, 6 | Segoe UI Emoji, Segoe UI Symbol; 7 | } 8 | 9 | ul, 10 | ol, 11 | li { 12 | list-style: none; 13 | } 14 | 15 | body { 16 | scroll-behavior: smooth; 17 | color: #24292e; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-osx-font-smoothing: grayscale; 20 | } 21 | 22 | .gt-container .gt-meta { 23 | margin-top: 0 !important; 24 | padding-top: 0 !important; 25 | } 26 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | productionSourceMap: false, 3 | // publicPath 注意修改为自己的根目录,如果直接用 username.github.io 域名则设置为 '/' 4 | // publicPath: '/' 5 | publicPath: process.env.NODE_ENV === 'production' ? '/treasure/gitleaf' : '/', 6 | chainWebpack: config => { 7 | // svg rule loader 8 | const svgRule = config.module.rule('svg') 9 | svgRule.uses.clear() 10 | svgRule.exclude.add(/node_modules/) 11 | svgRule 12 | .test(/\.svg$/) 13 | .use('svg-sprite-loader') 14 | .loader('svg-sprite-loader') 15 | .options({ 16 | symbolId: 'icon-[name]' 17 | }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/icons/svg/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | gitlife 10 | 11 | 12 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/SvgIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 34 | 35 | 44 | -------------------------------------------------------------------------------- /src/utils/documents.js: -------------------------------------------------------------------------------- 1 | export default { 2 | queryArchivesCount: ({ username, repository }) => ` 3 | query { 4 | repository(owner:"${username}", name: "${repository}") { 5 | issues(states:OPEN) { 6 | totalCount 7 | } 8 | } 9 | } 10 | `, 11 | queryMoodCount: ({ username, repository }) => ` 12 | query { 13 | repository(owner:"${username}", name: "${repository}") { 14 | issues(states:CLOSED, labels: Mood) { 15 | totalCount 16 | } 17 | } 18 | } 19 | `, 20 | queryFilterArchivesCount: ({ username, repository, label, milestone }) => ` 21 | { 22 | search(type: ISSUE, query: " 23 | user:${username} 24 | repo:${repository} 25 | state:open 26 | ${milestone ? 'milestone:' + milestone : ''} 27 | ${label ? 'label:' + label : ''} 28 | ") { 29 | issueCount 30 | } 31 | } 32 | ` 33 | } 34 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from './views/Home.vue' 4 | 5 | Vue.use(Router) 6 | 7 | export default new Router({ 8 | routes: [ 9 | { 10 | path: '/', 11 | name: 'home', 12 | component: Home 13 | }, 14 | { 15 | path: '/archives', 16 | name: 'archives', 17 | component: () => import('./views/Archive') 18 | }, 19 | { 20 | path: '/categories', 21 | name: 'categories', 22 | component: () => import('./views/Category') 23 | }, 24 | { 25 | path: '/tags', 26 | name: 'tags', 27 | component: () => import('./views/Tag') 28 | }, 29 | { 30 | path: '/mood', 31 | name: 'mood', 32 | component: () => import('./views/Mood') 33 | }, 34 | { 35 | path: '/friends', 36 | name: 'friends', 37 | component: () => import('./views/Friend') 38 | }, 39 | { 40 | path: '/about', 41 | name: 'about', 42 | component: () => import('./views/About') 43 | }, 44 | { 45 | path: '/post/:number', 46 | name: 'post', 47 | component: () => import('./views/Post.vue') 48 | } 49 | ] 50 | }) 51 | -------------------------------------------------------------------------------- /src/assets/lib/highlight.js: -------------------------------------------------------------------------------- 1 | import 'highlight.js/styles/atelier-dune-dark.css' 2 | 3 | import hljs from 'highlight.js/lib/highlight' 4 | import javascript from 'highlight.js/lib/languages/javascript' 5 | import xml from 'highlight.js/lib/languages/xml' 6 | import less from 'highlight.js/lib/languages/less' 7 | import css from 'highlight.js/lib/languages/css' 8 | import java from 'highlight.js/lib/languages/java' 9 | import python from 'highlight.js/lib/languages/python' 10 | import objectivec from 'highlight.js/lib/languages/objectivec' 11 | import markdown from 'highlight.js/lib/languages/markdown' 12 | import bash from 'highlight.js/lib/languages/bash' 13 | import json from 'highlight.js/lib/languages/json' 14 | import http from 'highlight.js/lib/languages/http' 15 | 16 | // 按需导入高亮语种 17 | hljs.registerLanguage('javascript', javascript) 18 | hljs.registerLanguage('xml', xml) 19 | hljs.registerLanguage('less', less) 20 | hljs.registerLanguage('css', css) 21 | hljs.registerLanguage('java', java) 22 | hljs.registerLanguage('python', python) 23 | hljs.registerLanguage('objectivec', objectivec) 24 | hljs.registerLanguage('markdown', markdown) 25 | hljs.registerLanguage('bash', bash) 26 | hljs.registerLanguage('json', json) 27 | hljs.registerLanguage('http', http) 28 | 29 | hljs.initHighlightingOnLoad() 30 | 31 | export default hljs 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitlife", 3 | "version": "1.0.0", 4 | "description": "A github style blog theme", 5 | "author": "蝉時雨 ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "vue-cli-service serve", 9 | "serve": "vue-cli-service serve", 10 | "build": "vue-cli-service build", 11 | "lint": "vue-cli-service lint", 12 | "svgo": "svgo -f src/assets/icons/svg --config=src/assets/icons/svgo.yml" 13 | }, 14 | "dependencies": { 15 | "core-js": "^2.6.5", 16 | "gitalk": "^1.5.0", 17 | "highlight.js": "^9.15.6", 18 | "ismobilejs": "^0.5.1", 19 | "leancloud-storage": "^3.12.0", 20 | "marked": "^0.6.2", 21 | "smooth-scroll": "^16.0.3", 22 | "timeago.js": "^4.0.0-beta.2", 23 | "valine": "^1.3.6", 24 | "vue": "^2.6.6", 25 | "vue-progressbar": "^0.7.5", 26 | "vue-router": "^3.0.1", 27 | "vuex": "^3.0.1" 28 | }, 29 | "devDependencies": { 30 | "@vue/cli-plugin-babel": "^3.5.0", 31 | "@vue/cli-plugin-eslint": "^3.5.0", 32 | "@vue/cli-service": "^3.5.0", 33 | "@vue/eslint-config-prettier": "^4.0.1", 34 | "babel-eslint": "^10.0.1", 35 | "eslint": "^5.8.0", 36 | "eslint-plugin-vue": "^5.0.0", 37 | "less": "^3.9.0", 38 | "less-loader": "^4.1.0", 39 | "svg-sprite-loader": "^4.1.3", 40 | "svgo": "^1.2.1", 41 | "vue-template-compiler": "^2.5.21" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/views/Archive.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 54 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import isMobile from 'ismobilejs' 3 | import AV from 'leancloud-storage' 4 | import VueProgressBar from 'vue-progressbar' 5 | import SmoothScroll from 'smooth-scroll' 6 | import App from './App.vue' 7 | import router from './router' 8 | import store from './store' 9 | import config from './config' 10 | 11 | // 全局样式与字体图标 12 | import 'gitalk/dist/gitalk.css' 13 | import './assets/style/layout.less' 14 | import './assets/style/github-markdown.css' 15 | import './assets/icons' 16 | 17 | // 配置全局变量 18 | Vue.config.productionTip = false 19 | Vue.prototype.$config = config 20 | Vue.prototype.$isMobile = isMobile.phone 21 | 22 | // 滚动到锚点 23 | const scrollOpts = { 24 | updateURL: false, 25 | emitEvents: false, 26 | durationMin: 1000, 27 | durationMax: 1200, 28 | easing: 'easeOutQuint' 29 | } 30 | const scroll = new SmoothScroll() 31 | Vue.prototype.$scroll = anchor => { 32 | scroll.animateScroll(anchor, scrollOpts) 33 | } 34 | 35 | // Register AV objects to the global 36 | window.AV = AV 37 | 38 | // Init Leancloud 39 | AV.init(config.leancloud) 40 | 41 | // 顶部进度条 42 | const options = { 43 | color: '#77B6FF', 44 | thickness: '2px', 45 | transition: { 46 | speed: '0.2s', 47 | opacity: '0.6s', 48 | termination: 300 49 | }, 50 | autoRevert: true, 51 | location: 'top', 52 | inverse: false 53 | } 54 | Vue.use(VueProgressBar, options) 55 | 56 | new Vue({ 57 | router, 58 | store, 59 | render: h => h(App) 60 | }).$mount('#app') 61 | -------------------------------------------------------------------------------- /src/components/MarkDown.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gitleaf - A github style blog theme 2 | 3 | [![Author](https://img.shields.io/badge/author-chanshiyucx-blue.svg?style=flat-square)](https://chanshiyu.com) [![QQ](https://img.shields.io/badge/QQ-1124590931-blue.svg?style=flat-square)](http://wpa.qq.com/msgrd?v=3&uin=&site=qq&menu=yes) [![Email](https://img.shields.io/badge/Emali%20me-me@chanshiyu.com-green.svg?style=flat-square)](me@chanshiyu.com) 4 | 5 | ![蝉時雨](https://i.loli.net/2019/04/14/5cb2a383d0915.png) 6 | 7 | Gitleaf 是一个 Github Style 的单页面博客应用程序,后台数据源依托于 [Github Issues](https://developer.github.com/v3/issues/),使用开源项目 [Gitalk](https://github.com/gitalk/gitalk) 作为博客评论系统。该主题基于 Github 全家桶,脱离服务器与数据库,关注内容本身,操作简单,免费食用。 8 | 9 | 技术栈:Github Api + Github Pages + Github Style + Gitalk 10 | 11 | 在线演示:[蝉時雨](https://chanshiyu.com/treasure/gitleaf) 12 | 13 | ## Getting Started 14 | 15 | 主题食用参考主题 Aurora,两者配置文件基本一致,**需要注意的是 Leancloud 需要新建一个表 Star 用于存放文章 star**:[食用指南](https://github.com/chanshiyucx/blog/issues/41) 16 | 17 | ### Installing 18 | 19 | ```bash 20 | git@github.com:chanshiyucx/gitleaf.git 21 | cd gitleaf 22 | npm install # or yarn 23 | ``` 24 | 25 | ### Configuration 26 | 27 | 修改目录 `src/config.js` 的配置文件,每个配置项都有详细说明。**注意修改 vue.config.js 的 publicPath**。 28 | 29 | 页面模板参考: [文章、关于、标签、分类、书单等模板](https://github.com/chanshiyucx/Blog/issues),第一次食用可以直接 Fork 预览效果。 30 | 31 | ### Preview 32 | 33 | ```bash 34 | npm start 35 | ``` 36 | 37 | 浏览器打开 `http://localhost:8000` 便可访问新的博客! 38 | 39 | ### Deployment 40 | 41 | ```bash 42 | npm run build 43 | ``` 44 | 45 | 打包完毕,将 `dist` 目录下生成的静态文件发布 `Github Pages` 或 `Coding Pages` 即可。 46 | 47 | Just enjoy it ฅ●ω●ฅ 48 | 49 | ## License 50 | 51 | This project is licensed under the MIT License. 52 | -------------------------------------------------------------------------------- /src/assets/icons/svg/share.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 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 | -------------------------------------------------------------------------------- /src/assets/icons/svg/chat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 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 | 50 | 51 | -------------------------------------------------------------------------------- /src/assets/icons/svg/tag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 14 | 15 | 18 | 19 | 20 | 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 | 50 | 51 | -------------------------------------------------------------------------------- /src/views/Friend.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | 78 | -------------------------------------------------------------------------------- /src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 84 | -------------------------------------------------------------------------------- /src/components/Pagination.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 28 | 29 | 75 | -------------------------------------------------------------------------------- /src/views/Mood.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 69 | 70 | 92 | -------------------------------------------------------------------------------- /src/utils/format.js: -------------------------------------------------------------------------------- 1 | import { format } from 'timeago.js' 2 | import config from '../config' 3 | 4 | /** 5 | * 格式化文章 6 | */ 7 | export const formatPost = post => { 8 | const { body, created_at } = post 9 | const temp = body.split('\r\n') 10 | const regex = /^\[(.+)\].*(http.*(?:jpg|jpeg|png|gif))/g 11 | const cover = regex.exec(temp[0]) 12 | post.cover = { 13 | title: cover && cover[1] ? cover[1] : '', 14 | src: cover && cover[2] ? cover[2] : config.defaultCover 15 | } 16 | post.desc = temp[2] || temp[0] 17 | post.created_at = format(created_at, 'zh_CN') 18 | return post 19 | } 20 | 21 | /** 22 | * 格式化时间 23 | */ 24 | export const formatTime = list => { 25 | list.forEach(o => (o.created_at = format(o.created_at, 'zh_CN'))) 26 | return list 27 | } 28 | 29 | /** 30 | * 格式化分类 31 | */ 32 | export const formatCategory = category => { 33 | category.forEach(o => { 34 | const desc = o.description.split('\r\n') 35 | o.summary = desc[0].split('summary:')[1] 36 | o.cover = desc[1].split('cover:')[1] 37 | o.created_at = format(o.created_at, 'zh_CN') 38 | }) 39 | return category 40 | } 41 | 42 | /** 43 | * 格式化书单 & 友链 & 关于 44 | */ 45 | export const formatPage = (data, type) => { 46 | if (!data.body) return 47 | if (type === 'about') return data 48 | 49 | let section = data.body.split('## ').filter(o => o.length) 50 | switch (type) { 51 | case 'book': 52 | section = section.map(o => { 53 | const content = o.split('\r\n').filter(o => o.length) 54 | return { 55 | name: content[0], 56 | author: content[1].split('author:')[1], 57 | published: content[2].split('published:')[1], 58 | progress: content[3].split('progress:')[1], 59 | rating: content[4].split('rating:')[1], 60 | postTitle: content[5].split('postTitle:')[1], 61 | postLink: content[6].split('postLink:')[1], 62 | cover: content[7].split('cover:')[1], 63 | desc: content[9].split('desc:')[1] 64 | } 65 | }) 66 | break 67 | case 'friend': 68 | section = section.map(o => { 69 | const content = o.split('\r\n').filter(o => o.length) 70 | return { 71 | name: content[0], 72 | link: content[1].split('link:')[1], 73 | cover: content[2].split('cover:')[1], 74 | avatar: content[3].split('avatar:')[1] 75 | } 76 | }) 77 | break 78 | } 79 | return section 80 | } 81 | -------------------------------------------------------------------------------- /src/components/ArchiveList.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 45 | 46 | 98 | -------------------------------------------------------------------------------- /src/views/Tag.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 75 | 94 | -------------------------------------------------------------------------------- /src/assets/icons/svg/inbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * ======================================== 4 | * 站点功能【必需】 5 | * ======================================== 6 | **/ 7 | 8 | /** 9 | * 站点标题 10 | */ 11 | title: '蝉时雨', 12 | subtitle: '蝉鸣如雨 花宵道中', 13 | 14 | /** 15 | * Github Issues 配置【文章、说说、书单、友链】 16 | * Github Issues api: https://developer.github.com/v3/issues/ 17 | **/ 18 | username: 'chanshiyucx', // github 用户名 19 | repository: 'blog', // 仓库地址 20 | // token 从中间任意位置拆开成两部分,避免 github 代码检测失效 21 | token: ['0ad1a0539c5b96fd18fa', 'aaafba9c7d1362a5746c'], 22 | 23 | /** 24 | * leancloud 配置 【文章浏览次数】 25 | */ 26 | leancloud: { 27 | appId: 'b6DWxsCOWuhurfp4YqbD5cDE-gzGzoHsz', 28 | appKey: 'h564RR5uVuJV5uSeP7oFTBye' 29 | }, 30 | 31 | /** 32 | * Gittalk 配置【评论功能】,详细文档参见:https://github.com/gitalk/gitalk 33 | */ 34 | gitalk: { 35 | clientID: '864b1c2cbc4e4aad9ed8', 36 | clientSecret: '6ca16373efa03347e11a96ff92e355c5cea189bb', 37 | repo: 'Comment', 38 | owner: 'chanshiyucx', 39 | admin: ['chanshiyucx'], 40 | distractionFreeMode: false // 是否开始无干扰模式【背景遮罩】 41 | }, 42 | 43 | /** 44 | * 顶部导航 45 | */ 46 | links: [ 47 | { 48 | name: 'Blog', 49 | url: 'https://chanshiyu.com/' 50 | }, 51 | { 52 | name: 'Github', 53 | url: 'https://github.com/chanshiyucx' 54 | }, 55 | { 56 | name: 'Aurora', 57 | url: 'https://github.com/chanshiyucx/aurora' 58 | }, 59 | { 60 | name: 'HeartBeat', 61 | url: 'https://github.com/chanshiyucx/heart-beat' 62 | }, 63 | { 64 | name: 'Gitlife', 65 | url: 'https://github.com/chanshiyucx/gitlife' 66 | } 67 | ], 68 | 69 | /** 70 | * 个人信息页面 71 | */ 72 | personal: { 73 | avatar: 'https://i.loli.net/2018/12/09/5c0cc2b4e0195.png', 74 | username: 'chanshiyu', 75 | nickname: '蝉時雨', 76 | description: 'Write the code, Change the world', 77 | location: 'Shenzhen, China', 78 | email: 'me@chanshiyu.com', 79 | site: 'https://chanshiyu.com', 80 | social: [ 81 | { 82 | icon: 'https://i.loli.net/2019/01/25/5c4b2982b5687.png', 83 | link: 'https://github.com/chanshiyucx' 84 | }, 85 | { 86 | icon: 'https://i.loli.net/2018/12/09/5c0cc518dc4f4.png', 87 | link: 'https://www.zhihu.com/people/ichanshiyu/activities' 88 | }, 89 | { 90 | icon: 'https://i.loli.net/2018/12/09/5c0cc51ae4f0c.png', 91 | link: 'https://music.163.com/#/user/home?id=103060582' 92 | } 93 | ], 94 | footer: [ 95 | { 96 | name: 'Home', 97 | link: 'https://chanshiyu.com' 98 | }, 99 | { 100 | name: 'Github', 101 | link: 'https://github.com/chanshiyucx' 102 | }, 103 | { 104 | name: 'Zhihu', 105 | link: 'https://www.zhihu.com/people/ichanshiyu/activities' 106 | } 107 | ] 108 | }, 109 | 110 | /** 111 | * 评论功能是否启用 112 | */ 113 | comment: { 114 | post: true, 115 | mood: true, 116 | friend: true, 117 | about: true 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/components/Comment.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 89 | 90 | 116 | -------------------------------------------------------------------------------- /src/assets/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/views/Category.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 91 | 152 | -------------------------------------------------------------------------------- /src/assets/icons/svg/calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 56 | 57 | 183 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 117 | 185 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 68 | 213 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 87 | 88 | 284 | -------------------------------------------------------------------------------- /src/utils/services.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | import config from '../config' 3 | import documents from './documents' 4 | 5 | const BAST_URL = 'https://api.github.com' 6 | const GRAPHQL_URL = `${BAST_URL}/graphql` 7 | const GITHUB_API = `${BAST_URL}/repos` 8 | 9 | const { username, repository, token } = config 10 | const blog = `${GITHUB_API}/${username}/${repository}` 11 | const access_token = token.join('') 12 | const open = `state=open&access_token=${access_token}` 13 | const closed = `state=closed&access_token=${access_token}` 14 | const isDev = window.location.href.includes('localhost') 15 | 16 | // 状态检测 17 | const checkStatus = response => { 18 | if (response.status >= 200 && response.status < 300) return response 19 | const error = new Error(response.statusText) 20 | error.response = response 21 | throw error 22 | } 23 | 24 | // 构建 GraphQL 25 | const createCall = async document => { 26 | try { 27 | const payload = JSON.stringify({ query: document }) 28 | const response = await fetch(GRAPHQL_URL, { 29 | method: 'POST', 30 | headers: { 31 | Authorization: `token ${access_token}` 32 | }, 33 | body: payload 34 | }) 35 | checkStatus(response) 36 | const body = await response.json() 37 | return body.data 38 | } catch (err) { 39 | console.log(err) 40 | } 41 | } 42 | 43 | // 获取文章数量 44 | export const queryArchivesCount = () => createCall(documents.queryArchivesCount({ username, repository })) 45 | 46 | // 获取心情数量 47 | export const queryMoodCount = () => createCall(documents.queryMoodCount({ username, repository })) 48 | 49 | // 按分类 & 标签筛选文章 50 | export const queryFilterArchivesCount = ({ label, milestone }) => 51 | createCall(documents.queryFilterArchivesCount({ username, repository, label, milestone })) 52 | 53 | // 获取文章列表 54 | export const queryPosts = async ({ page = 1, pageSize = 10, filter = '' }) => { 55 | try { 56 | const url = `${blog}/issues?${open}&page=${page}&per_page=${pageSize}${filter}` 57 | const response = await fetch(url) 58 | checkStatus(response) 59 | const data = await response.json() 60 | return data 61 | } catch (err) { 62 | console.log(err) 63 | } 64 | } 65 | 66 | // 获取单篇文章 67 | export const queryPost = async number => { 68 | try { 69 | const url = `${blog}/issues/${number}?${open}` 70 | const response = await fetch(url) 71 | checkStatus(response) 72 | const data = await response.json() 73 | return data 74 | } catch (err) { 75 | console.log(err) 76 | } 77 | } 78 | 79 | // 搜索文章 80 | export const searchPost = async keyword => { 81 | try { 82 | const url = `${BAST_URL}/search/issues?q=${keyword}+in:title+state:open+user:${username}+author:${username}&access_token=${access_token}` 83 | const response = await fetch(url) 84 | checkStatus(response) 85 | const data = await response.json() 86 | return data 87 | } catch (err) { 88 | console.log(err) 89 | } 90 | } 91 | 92 | // 获取分类 93 | export const queryCategory = async () => { 94 | try { 95 | const url = `${blog}/milestones?access_token=${access_token}` 96 | const response = await fetch(url) 97 | checkStatus(response) 98 | const data = await response.json() 99 | return data 100 | } catch (err) { 101 | console.log(err) 102 | } 103 | } 104 | 105 | // 获取标签 106 | export const queryTag = async () => { 107 | try { 108 | const url = `${blog}/labels?access_token=${access_token}` 109 | const response = await fetch(url) 110 | checkStatus(response) 111 | const data = await response.json() 112 | return data 113 | } catch (err) { 114 | console.log(err) 115 | } 116 | } 117 | 118 | // 获取心情 119 | export const queryMood = async ({ page = 1, pageSize = 10 }) => { 120 | try { 121 | const url = `${blog}/issues?${closed}&labels=mood&page=${page}&per_page=${pageSize}` 122 | const response = await fetch(url) 123 | checkStatus(response) 124 | const data = await response.json() 125 | return data 126 | } catch (err) { 127 | console.log(err) 128 | } 129 | } 130 | 131 | // 获取书单 & 友链 & 关于 132 | export const queryPage = async type => { 133 | try { 134 | const upperType = type.replace(/^\S/, s => s.toUpperCase()) 135 | const url = `${blog}/issues?${closed}&labels=${upperType}` 136 | const response = await fetch(url) 137 | checkStatus(response) 138 | const data = await response.json() 139 | return data[0] 140 | } catch (err) { 141 | console.log(err) 142 | } 143 | } 144 | 145 | // 文章热度 146 | export const queryHot = async (postList, isAdd) => { 147 | return new Promise(resolve => { 148 | if (isDev) return resolve(postList) 149 | const seq = postList.map(o => { 150 | return new Promise(resolve => { 151 | const query = new AV.Query('Counter') 152 | const Counter = AV.Object.extend('Counter') 153 | const { title, id } = o 154 | query.equalTo('id', id) 155 | query 156 | .find() 157 | .then(res => { 158 | if (res.length > 0) { 159 | // 已存在则返回热度 160 | const counter = res[0] 161 | // 是否增接热度 162 | if (isAdd) { 163 | counter 164 | .increment('time', 1) 165 | .save(null, { fetchWhenSave: true }) 166 | .then(counter => { 167 | o.times = counter.get('time') 168 | resolve(o) 169 | }) 170 | .catch(console.error) 171 | } else { 172 | o.times = counter.get('time') 173 | resolve(o) 174 | } 175 | } else { 176 | // 不存在则新建 177 | const newcounter = new Counter() 178 | newcounter.set('title', title) 179 | newcounter.set('id', id) 180 | newcounter.set('time', 1) 181 | newcounter.set('site', location.href) 182 | newcounter 183 | .save() 184 | .then(() => resolve(o)) 185 | .catch(console.error) 186 | } 187 | }) 188 | .catch(console.error) 189 | }).catch(console.error) 190 | }) 191 | Promise.all(seq) 192 | .then(data => resolve(data)) 193 | .catch(console.error) 194 | }).catch(console.error) 195 | } 196 | 197 | // 文章 Star 198 | export const queryStar = async ({ id, isAdd, star }) => { 199 | return new Promise(resolve => { 200 | if (isDev) return resolve() 201 | const query = new AV.Query('Star') 202 | const Star = AV.Object.extend('Star') 203 | query.equalTo('id', id) 204 | query 205 | .first() 206 | .then(res => { 207 | if (res) { 208 | if (isAdd === true) { 209 | res 210 | .increment('time', 1) 211 | .save(null, { fetchWhenSave: true }) 212 | .then(counter => { 213 | console.log(counter.get('time')) 214 | resolve(counter.get('time')) 215 | }) 216 | .catch(console.error) 217 | } else if (isAdd === false) { 218 | res 219 | .set('time', star - 1) 220 | .save(null, { fetchWhenSave: true }) 221 | .then(counter => { 222 | console.log(counter.get('time')) 223 | resolve(counter.get('time')) 224 | }) 225 | .catch(console.error) 226 | } else { 227 | resolve(res.get('time')) 228 | } 229 | } else { 230 | // 不存在则新建 231 | const newStar = new Star() 232 | newStar.set('id', id) 233 | newStar.set('time', 0) 234 | newStar 235 | .save() 236 | .then(() => resolve(0)) 237 | .catch(console.error) 238 | } 239 | }) 240 | .catch(console.error) 241 | }).catch(console.error) 242 | } 243 | 244 | // 访问来源 245 | export const visitor = async referrer => { 246 | return new Promise(resolve => { 247 | if (isDev) return resolve() 248 | const query = new AV.Query('Visitor') 249 | const Visitor = AV.Object.extend('Visitor') 250 | query.equalTo('referrer', referrer) 251 | query 252 | .first() 253 | .then(res => { 254 | if (res) { 255 | res 256 | .increment('time', 1) 257 | .save(null, { fetchWhenSave: true }) 258 | .then(() => resolve()) 259 | .catch(console.error) 260 | } else { 261 | // 不存在则新建 262 | const newVisitor = new Visitor() 263 | newVisitor.set('referrer', referrer) 264 | newVisitor.set('time', 1) 265 | newVisitor 266 | .save() 267 | .then(() => resolve()) 268 | .catch(console.error) 269 | } 270 | }) 271 | .catch(console.error) 272 | }).catch(console.error) 273 | } 274 | -------------------------------------------------------------------------------- /src/views/Post.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 106 | 294 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { 4 | queryArchivesCount, 5 | queryMoodCount, 6 | queryFilterArchivesCount, 7 | queryPosts, 8 | queryPost, 9 | queryHot, 10 | queryStar, 11 | queryCategory, 12 | queryTag, 13 | queryMood, 14 | queryPage, 15 | searchPost, 16 | visitor 17 | } from './utils/services' 18 | import { formatPost, formatTime, formatCategory, formatPage } from './utils/format' 19 | 20 | Vue.use(Vuex) 21 | 22 | export default new Vuex.Store({ 23 | state: { 24 | loading: false, 25 | archivesCount: 0, 26 | filterArchivesCount: 0, 27 | moodCount: 0, 28 | post: {}, 29 | archives: { 30 | pageSize: 20, 31 | page: 0, 32 | posts: [], 33 | list: [] 34 | }, 35 | filterArchives: { 36 | pageSize: 20, 37 | page: 0, 38 | posts: [], 39 | list: [] 40 | }, 41 | mood: { 42 | pageSize: 20, 43 | page: 0, 44 | moods: [], 45 | list: [] 46 | }, 47 | recentPost: [], 48 | searchPost: [], 49 | categories: [], 50 | tags: [], 51 | friend: [], 52 | about: {} 53 | }, 54 | mutations: { 55 | // 设置 loading 状态 56 | setLoading(state, loading) { 57 | state.loading = loading 58 | }, 59 | // 设置文章数量 60 | setArchivesCount(state, payload) { 61 | state.archivesCount = payload 62 | }, 63 | // 筛选文章数量 64 | setFilterArchivesCount(state, payload) { 65 | state.filterArchivesCount = payload 66 | }, 67 | // 设置心情数量 68 | setMoodCount(state, payload) { 69 | state.moodCount = payload 70 | }, 71 | // 设置归档 72 | setArchives(state, payload) { 73 | state.archives = { 74 | ...state.archives, 75 | ...payload 76 | } 77 | }, 78 | resetArchives(state) { 79 | state.archives.page = 0 80 | }, 81 | // 分类 & 标签筛选文章 82 | setFilterArchives(state, payload) { 83 | state.filterArchives = { 84 | ...state.filterArchives, 85 | ...payload 86 | } 87 | }, 88 | resetFilterArchives(state) { 89 | state.filterArchivesCount = 0 90 | state.filterArchives = { 91 | pageSize: 20, 92 | page: 0, 93 | posts: [], 94 | list: [] 95 | } 96 | }, 97 | // 设置近期文章 98 | setRecentPost(state, payload) { 99 | state.recentPost = payload 100 | }, 101 | // 设置搜索文章 102 | setSearchPost(state, payload) { 103 | state.searchPost = payload 104 | }, 105 | // 设置当前文章 106 | setPost(state, payload) { 107 | state.post = { ...payload } 108 | }, 109 | // 设置分类 110 | setCategories(state, payload) { 111 | state.categories = payload 112 | }, 113 | // 设置标签 114 | setTags(state, payload) { 115 | state.tags = payload 116 | }, 117 | // 设置心情 118 | setMood(state, payload) { 119 | state.mood = { 120 | ...state.mood, 121 | ...payload 122 | } 123 | }, 124 | resetMood(state) { 125 | state.mood.page = 0 126 | }, 127 | // 设置页面 128 | setPage(state, { type, data }) { 129 | state[type] = data 130 | } 131 | }, 132 | actions: { 133 | // 获取文章总数 134 | async queryArchivesCount({ commit }) { 135 | const data = await queryArchivesCount() 136 | const count = data.repository.issues.totalCount 137 | commit('setArchivesCount', count) 138 | }, 139 | // 获取 分类 & 标签筛选文章数量 140 | async queryFilterArchivesCount({ commit }, payload) { 141 | const data = await queryFilterArchivesCount(payload) 142 | const count = data.search.issueCount 143 | commit('resetFilterArchives') 144 | commit('setFilterArchivesCount', count) 145 | }, 146 | // 获取心情总数 147 | async queryMoodCount({ commit }) { 148 | const data = await queryMoodCount() 149 | const count = data.repository.issues.totalCount 150 | commit('setMoodCount', count) 151 | }, 152 | // 归档文章 153 | async queryArchives({ state, dispatch, commit }, { type }) { 154 | const { pageSize, page, list } = state.archives 155 | const queryPage = type === 'prev' ? page - 1 : page + 1 156 | // 如果缓存列表里已存在 157 | if (list[queryPage]) { 158 | return commit('setArchives', { 159 | posts: list[queryPage], 160 | page: queryPage 161 | }) 162 | } 163 | 164 | commit('setLoading', true) 165 | let posts = await dispatch('queryPosts', { 166 | pageSize, 167 | page: queryPage 168 | }) 169 | commit('setLoading', false) 170 | 171 | if (posts.length === 0) return 172 | list[queryPage] = posts 173 | commit('setArchives', { 174 | page: queryPage, 175 | posts, 176 | list 177 | }) 178 | 179 | posts = await dispatch('queryHot', { posts }) 180 | commit('setArchives', { posts }) 181 | }, 182 | 183 | // 分类 & 标签筛选文章 184 | async queryFilterArchives({ state, dispatch, commit }, { type, filter }) { 185 | const { pageSize, page, list } = state.filterArchives 186 | const queryPage = type === 'prev' ? page - 1 : page + 1 187 | // 如果缓存列表里已存在 188 | if (list[queryPage]) { 189 | return commit('setFilterArchives', { 190 | posts: list[queryPage], 191 | page: queryPage 192 | }) 193 | } 194 | 195 | commit('setLoading', true) 196 | let posts = await dispatch('queryPosts', { 197 | pageSize, 198 | page: queryPage, 199 | filter 200 | }) 201 | commit('setLoading', false) 202 | 203 | if (posts.length === 0) return 204 | list[queryPage] = posts 205 | commit('setFilterArchives', { 206 | page: queryPage, 207 | posts, 208 | list 209 | }) 210 | 211 | posts = await dispatch('queryHot', { posts }) 212 | commit('setFilterArchives', { posts }) 213 | }, 214 | // 获取文章列表 215 | async queryPosts(context, payload) { 216 | let data = await queryPosts(payload) 217 | data.forEach(formatPost) 218 | return data 219 | }, 220 | // 获取心情列表 221 | async queryMood({ state, commit }, { type }) { 222 | const { pageSize, page, list } = state.mood 223 | const queryPage = type === 'prev' ? page - 1 : page + 1 224 | // 如果缓存列表里已存在 225 | if (list[queryPage]) { 226 | return commit('setMood', { 227 | moods: list[queryPage], 228 | page: queryPage 229 | }) 230 | } 231 | 232 | commit('setLoading', true) 233 | let moods = await queryMood({ 234 | pageSize, 235 | page: queryPage 236 | }) 237 | commit('setLoading', false) 238 | 239 | if (moods.length === 0) return 240 | moods = formatTime(moods) 241 | list[queryPage] = moods 242 | commit('setMood', { 243 | page: queryPage, 244 | moods, 245 | list 246 | }) 247 | }, 248 | // 获取文章详情 249 | async queryPost({ dispatch, commit }, { number }) { 250 | let post = await queryPost(number) 251 | post = formatPost(post) 252 | commit('setPost', post) 253 | post.star = await queryStar({ id: post.id }) 254 | let posts = await dispatch('queryHot', { posts: [post], isAdd: true }) 255 | commit('setPost', posts[0]) 256 | }, 257 | // 获取文章热度 258 | async queryHot(context, { posts, isAdd = false }) { 259 | return await queryHot(posts, isAdd) 260 | }, 261 | // 获取近期文章 262 | async queryRecentPost({ dispatch, commit }) { 263 | let posts = await dispatch('queryPosts', { 264 | pageSize: 6, 265 | page: 1 266 | }) 267 | commit('setRecentPost', posts) 268 | posts = await dispatch('queryHot', { posts }) 269 | commit('setRecentPost', posts) 270 | }, 271 | // 搜索文章 272 | async searchPost({ commit }, { keyword }) { 273 | const data = await searchPost(keyword) 274 | commit('setSearchPost', data.items) 275 | }, 276 | // 获取分类 277 | async queryCategory({ commit }) { 278 | let data = await queryCategory() 279 | data = formatCategory(data) 280 | commit('setCategories', data) 281 | }, 282 | // 获取标签 283 | async queryTag({ commit }) { 284 | let data = await queryTag() 285 | data = data.filter( 286 | o => o.name !== 'Mood' && o.name !== 'Friend' && o.name !== 'Book' && o.name !== 'About' 287 | ) 288 | commit('setTags', data) 289 | }, 290 | // 获取书单 & 友链 & 关于 291 | async queryPage({ commit }, { type }) { 292 | let data = await queryPage(type) 293 | data = formatPage(data, type) 294 | commit('setPage', { type, data }) 295 | }, 296 | // 文章 Star 297 | async postStar({ state, commit }, { isAdd }) { 298 | const { post } = state 299 | post.star = await queryStar({ id: post.id, isAdd, star: post.star }) 300 | commit('setPost', post) 301 | }, 302 | // 统计访问来源 303 | async visitorStatistics(context, payload) { 304 | await visitor(payload) 305 | } 306 | }, 307 | getters: { 308 | loading: state => state.loading, 309 | archivesCount: state => state.archivesCount, 310 | filterArchivesCount: state => state.filterArchivesCount, 311 | moodCount: state => state.moodCount, 312 | archives: state => state.archives, 313 | filterArchives: state => state.filterArchives, 314 | recentPost: state => state.recentPost, 315 | searchPost: state => state.searchPost, 316 | post: state => state.post, 317 | categories: state => state.categories, 318 | tags: state => state.tags, 319 | mood: state => state.mood, 320 | friend: state => state.friend, 321 | about: state => state.about 322 | } 323 | }) 324 | -------------------------------------------------------------------------------- /src/assets/style/github-markdown.css: -------------------------------------------------------------------------------- 1 | /* https://github.com/sindresorhus/github-markdown-css */ 2 | 3 | .markdown-body h1 .anchor, 4 | .markdown-body h2 .anchor, 5 | .markdown-body h3 .anchor, 6 | .markdown-body h4 .anchor, 7 | .markdown-body h5 .anchor, 8 | .markdown-body h6 .anchor { 9 | margin-top: 4px; 10 | color: #1b1f23; 11 | vertical-align: middle; 12 | visibility: hidden; 13 | } 14 | 15 | .markdown-body h1:hover .anchor, 16 | .markdown-body h2:hover .anchor, 17 | .markdown-body h3:hover .anchor, 18 | .markdown-body h4:hover .anchor, 19 | .markdown-body h5:hover .anchor, 20 | .markdown-body h6:hover .anchor { 21 | text-decoration: none; 22 | } 23 | 24 | .markdown-body h1:hover .anchor, 25 | .markdown-body h2:hover .anchor, 26 | .markdown-body h3:hover .anchor, 27 | .markdown-body h4:hover .anchor, 28 | .markdown-body h5:hover .anchor, 29 | .markdown-body h6:hover .anchor { 30 | visibility: visible; 31 | } 32 | 33 | .markdown-body { 34 | -ms-text-size-adjust: 100%; 35 | -webkit-text-size-adjust: 100%; 36 | color: #24292e; 37 | line-height: 1.5; 38 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, 39 | Segoe UI Emoji, Segoe UI Symbol; 40 | font-size: 16px; 41 | line-height: 1.5; 42 | word-wrap: break-word; 43 | } 44 | 45 | .markdown-body .anchor { 46 | float: left; 47 | line-height: 1; 48 | margin-left: -20px; 49 | padding-right: 4px; 50 | } 51 | 52 | .markdown-body .anchor:focus { 53 | outline: none; 54 | } 55 | 56 | .markdown-body details { 57 | display: block; 58 | } 59 | 60 | .markdown-body summary { 61 | display: list-item; 62 | } 63 | 64 | .markdown-body a { 65 | background-color: transparent; 66 | } 67 | 68 | .markdown-body a:active, 69 | .markdown-body a:hover { 70 | outline-width: 0; 71 | } 72 | 73 | .markdown-body strong { 74 | font-weight: inherit; 75 | font-weight: bolder; 76 | } 77 | 78 | .markdown-body h1 { 79 | font-size: 2em; 80 | margin: 0.67em 0; 81 | } 82 | 83 | .markdown-body img { 84 | border-style: none; 85 | } 86 | 87 | .markdown-body code, 88 | .markdown-body kbd, 89 | .markdown-body pre { 90 | font-family: 'Roboto Mono', 'PT Mono', Consolas, monospace; 91 | font-size: 1em; 92 | } 93 | 94 | .markdown-body pre span { 95 | font-family: 'Roboto Mono', 'PT Mono', Consolas, monospace; 96 | } 97 | 98 | .markdown-body hr { 99 | box-sizing: content-box; 100 | height: 0; 101 | overflow: visible; 102 | } 103 | 104 | .markdown-body * { 105 | box-sizing: border-box; 106 | } 107 | 108 | .markdown-body input { 109 | font-family: inherit; 110 | font-size: inherit; 111 | line-height: inherit; 112 | } 113 | 114 | .markdown-body a { 115 | color: #0366d6; 116 | text-decoration: none; 117 | } 118 | 119 | .markdown-body a:hover { 120 | text-decoration: underline; 121 | } 122 | 123 | .markdown-body strong { 124 | font-weight: 600; 125 | } 126 | 127 | .markdown-body hr { 128 | background: transparent; 129 | border: 0; 130 | border-bottom: 1px solid #dfe2e5; 131 | height: 0; 132 | margin: 15px 0; 133 | overflow: hidden; 134 | } 135 | 136 | .markdown-body hr:before { 137 | content: ''; 138 | display: table; 139 | } 140 | 141 | .markdown-body hr:after { 142 | clear: both; 143 | content: ''; 144 | display: table; 145 | } 146 | 147 | .markdown-body table { 148 | border-collapse: collapse; 149 | border-spacing: 0; 150 | } 151 | 152 | .markdown-body td, 153 | .markdown-body th { 154 | padding: 0; 155 | } 156 | 157 | .markdown-body details summary { 158 | cursor: pointer; 159 | } 160 | 161 | .markdown-body h1, 162 | .markdown-body h2, 163 | .markdown-body h3, 164 | .markdown-body h4, 165 | .markdown-body h5, 166 | .markdown-body h6 { 167 | margin-bottom: 0; 168 | margin-top: 0; 169 | } 170 | 171 | .markdown-body h1 { 172 | font-size: 32px; 173 | } 174 | 175 | .markdown-body h1, 176 | .markdown-body h2 { 177 | font-weight: 600; 178 | } 179 | 180 | .markdown-body h2 { 181 | font-size: 24px; 182 | } 183 | 184 | .markdown-body h3 { 185 | font-size: 20px; 186 | } 187 | 188 | .markdown-body h3, 189 | .markdown-body h4 { 190 | font-weight: 600; 191 | } 192 | 193 | .markdown-body h4 { 194 | font-size: 16px; 195 | } 196 | 197 | .markdown-body h5 { 198 | font-size: 14px; 199 | } 200 | 201 | .markdown-body h5, 202 | .markdown-body h6 { 203 | font-weight: 600; 204 | } 205 | 206 | .markdown-body h6 { 207 | font-size: 12px; 208 | } 209 | 210 | .markdown-body p { 211 | margin-bottom: 10px; 212 | margin-top: 0; 213 | } 214 | 215 | .markdown-body blockquote { 216 | margin: 0; 217 | } 218 | 219 | .markdown-body ol, 220 | .markdown-body ul { 221 | margin-bottom: 0; 222 | margin-top: 0; 223 | padding-left: 0; 224 | } 225 | 226 | .markdown-body ol ol, 227 | .markdown-body ul ol { 228 | list-style-type: lower-roman; 229 | } 230 | 231 | .markdown-body ol ol ol, 232 | .markdown-body ol ul ol, 233 | .markdown-body ul ol ol, 234 | .markdown-body ul ul ol { 235 | list-style-type: lower-alpha; 236 | } 237 | 238 | .markdown-body dd { 239 | margin-left: 0; 240 | } 241 | 242 | .markdown-body code, 243 | .markdown-body pre { 244 | font-family: 'Roboto Mono', 'PT Mono', Consolas, monospace; 245 | font-size: 12px; 246 | } 247 | 248 | .markdown-body pre { 249 | margin-bottom: 0; 250 | margin-top: 0; 251 | } 252 | 253 | .markdown-body .lh-condensed { 254 | line-height: 1.25 !important; 255 | } 256 | 257 | .markdown-body:before { 258 | content: ''; 259 | display: table; 260 | } 261 | 262 | .markdown-body:after { 263 | clear: both; 264 | content: ''; 265 | display: table; 266 | } 267 | 268 | .markdown-body > :first-child { 269 | margin-top: 0 !important; 270 | } 271 | 272 | .markdown-body > :last-child { 273 | margin-bottom: 0 !important; 274 | } 275 | 276 | .markdown-body a:not([href]) { 277 | color: inherit; 278 | text-decoration: none; 279 | } 280 | 281 | .markdown-body blockquote, 282 | .markdown-body dl, 283 | .markdown-body ol, 284 | .markdown-body p, 285 | .markdown-body pre, 286 | .markdown-body table, 287 | .markdown-body ul { 288 | margin-bottom: 16px; 289 | margin-top: 0; 290 | } 291 | 292 | .markdown-body hr { 293 | background-color: #e1e4e8; 294 | border: 0; 295 | height: 0.25em; 296 | margin: 24px 0; 297 | padding: 0; 298 | } 299 | 300 | .markdown-body blockquote { 301 | border-left: 0.25em solid #dfe2e5; 302 | color: #6a737d; 303 | padding: 0 1em; 304 | } 305 | 306 | .markdown-body blockquote > :first-child { 307 | margin-top: 0; 308 | } 309 | 310 | .markdown-body blockquote > :last-child { 311 | margin-bottom: 0; 312 | } 313 | 314 | .markdown-body h1, 315 | .markdown-body h2, 316 | .markdown-body h3, 317 | .markdown-body h4, 318 | .markdown-body h5, 319 | .markdown-body h6 { 320 | font-weight: 600; 321 | line-height: 1.25; 322 | margin-bottom: 16px; 323 | margin-top: 24px; 324 | } 325 | 326 | .markdown-body h1 { 327 | font-size: 2em; 328 | } 329 | 330 | .markdown-body h1, 331 | .markdown-body h2 { 332 | border-bottom: 1px solid #eaecef; 333 | padding-bottom: 0.3em; 334 | } 335 | 336 | .markdown-body h2 { 337 | font-size: 1.5em; 338 | } 339 | 340 | .markdown-body h3 { 341 | font-size: 1.25em; 342 | } 343 | 344 | .markdown-body h4 { 345 | font-size: 1em; 346 | } 347 | 348 | .markdown-body h5 { 349 | font-size: 0.875em; 350 | } 351 | 352 | .markdown-body h6 { 353 | color: #6a737d; 354 | font-size: 0.85em; 355 | } 356 | 357 | .markdown-body ol, 358 | .markdown-body ul { 359 | padding-left: 2em; 360 | } 361 | 362 | .markdown-body ol ol, 363 | .markdown-body ol ul, 364 | .markdown-body ul ol, 365 | .markdown-body ul ul { 366 | margin-bottom: 0; 367 | margin-top: 0; 368 | } 369 | 370 | .markdown-body li { 371 | word-wrap: break-all; 372 | } 373 | 374 | .markdown-body li > p { 375 | margin-top: 16px; 376 | } 377 | 378 | .markdown-body li + li { 379 | margin-top: 0.25em; 380 | } 381 | 382 | .markdown-body dl { 383 | padding: 0; 384 | } 385 | 386 | .markdown-body dl dt { 387 | font-size: 1em; 388 | font-style: italic; 389 | font-weight: 600; 390 | margin-top: 16px; 391 | padding: 0; 392 | } 393 | 394 | .markdown-body dl dd { 395 | margin-bottom: 16px; 396 | padding: 0 16px; 397 | } 398 | 399 | .markdown-body table { 400 | display: block; 401 | overflow: auto; 402 | width: 100%; 403 | } 404 | 405 | .markdown-body table th { 406 | font-weight: 600; 407 | } 408 | 409 | .markdown-body table td, 410 | .markdown-body table th { 411 | border: 1px solid #dfe2e5; 412 | padding: 6px 13px; 413 | } 414 | 415 | .markdown-body table tr { 416 | background-color: #fff; 417 | border-top: 1px solid #c6cbd1; 418 | } 419 | 420 | .markdown-body table tr:nth-child(2n) { 421 | background-color: #f6f8fa; 422 | } 423 | 424 | .markdown-body img { 425 | background-color: #fff; 426 | box-sizing: content-box; 427 | max-width: 100%; 428 | } 429 | 430 | .markdown-body img[align='right'] { 431 | padding-left: 20px; 432 | } 433 | 434 | .markdown-body img[align='left'] { 435 | padding-right: 20px; 436 | } 437 | 438 | .markdown-body code { 439 | background-color: rgba(27, 31, 35, 0.05); 440 | border-radius: 3px; 441 | font-size: 85%; 442 | margin: 0; 443 | padding: 0.2em 0.4em; 444 | } 445 | 446 | .markdown-body pre { 447 | word-wrap: normal; 448 | } 449 | 450 | .markdown-body pre > code { 451 | background: transparent; 452 | border: 0; 453 | font-size: 100%; 454 | margin: 0; 455 | padding: 0; 456 | white-space: pre; 457 | word-break: normal; 458 | } 459 | 460 | .markdown-body pre { 461 | word-break: normal; 462 | } 463 | 464 | .markdown-body pre, 465 | .markdown-body pre { 466 | background-color: #f6f8fa; 467 | border-radius: 3px; 468 | font-size: 85%; 469 | line-height: 1.45; 470 | overflow: auto; 471 | padding: 16px; 472 | } 473 | 474 | .markdown-body pre code { 475 | background-color: transparent; 476 | border: 0; 477 | display: inline; 478 | line-height: inherit; 479 | margin: 0; 480 | max-width: auto; 481 | overflow: visible; 482 | padding: 0; 483 | word-wrap: normal; 484 | } 485 | 486 | .markdown-body hr { 487 | border-bottom-color: #eee; 488 | } 489 | --------------------------------------------------------------------------------