├── .npmignore ├── .gitignore ├── styles ├── mixins.styl ├── mobile.styl ├── arrow.styl ├── config.styl ├── code.styl ├── custom-blocks.styl └── theme.styl ├── screenshot ├── home.jpg ├── page.jpg ├── post.jpg └── tag.jpg ├── assets ├── images │ ├── 404.jpg │ ├── world.png │ └── GitHub.svg └── fonts │ ├── iconfont.eot │ ├── iconfont.ttf │ ├── iconfont.woff │ ├── iconfont.css │ ├── iconfont.svg │ └── iconfont.js ├── comment ├── const.js ├── assets │ └── icon │ │ ├── reply.svg │ │ ├── heart_on.svg │ │ ├── edit.svg │ │ ├── arrow_down.svg │ │ ├── heart.svg │ │ ├── github.svg │ │ └── tip.svg ├── components │ ├── Action.vue │ ├── Button.vue │ ├── Svg.vue │ └── Comment.vue ├── i18n │ ├── index.js │ ├── zh-TW.json │ ├── zh-CN.json │ ├── en.json │ ├── es-ES.json │ ├── fr.json │ └── ru.json ├── util.js ├── graphql │ └── getComments.js ├── index.vue ├── index.styl └── mixin │ └── index.js ├── .github └── issue_template.md ├── bin ├── template.yml ├── index.js └── createPost.js ├── util ├── comment.mixin.js ├── client.js ├── plugin.js └── index.js ├── layouts ├── Tag.vue ├── Category.vue ├── Weekly.vue ├── Categories.vue ├── Layout.vue ├── 404.vue ├── Activity.vue ├── Tags.vue ├── Page.vue └── Home.vue ├── .release-it.json ├── components ├── ContentWrapper.vue ├── DropdownTransition.vue ├── TagNode.vue ├── Footer.vue ├── Date.vue ├── List.vue ├── Tag.vue ├── NavBar.vue ├── Brand.vue ├── Github.vue ├── NavLink.vue ├── SidebarButton.vue ├── LayoutContainer.vue ├── Tab.vue ├── SidebarGroup.vue ├── GithubCard.vue ├── ListContainer.vue ├── Pagination.vue ├── Sidebar.vue ├── SidebarLink.vue ├── Article.vue ├── NavLinks.vue ├── PopOver.vue ├── SearchBox.vue └── Tabs.vue ├── LICENSE ├── package.json ├── index.js ├── CHANGELOG.md └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs 3 | screenshot -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.lock 2 | node_modules 3 | .vscode 4 | package-lock.json 5 | yarn.lock -------------------------------------------------------------------------------- /styles/mixins.styl: -------------------------------------------------------------------------------- 1 | @require './config' 2 | 3 | px2rem(px) 4 | unit(px / $baseFontSize, 'rem') -------------------------------------------------------------------------------- /screenshot/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/HEAD/screenshot/home.jpg -------------------------------------------------------------------------------- /screenshot/page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/HEAD/screenshot/page.jpg -------------------------------------------------------------------------------- /screenshot/post.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/HEAD/screenshot/post.jpg -------------------------------------------------------------------------------- /screenshot/tag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/HEAD/screenshot/tag.jpg -------------------------------------------------------------------------------- /assets/images/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/HEAD/assets/images/404.jpg -------------------------------------------------------------------------------- /assets/images/world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/HEAD/assets/images/world.png -------------------------------------------------------------------------------- /comment/const.js: -------------------------------------------------------------------------------- 1 | export const GT_ACCESS_TOKEN = 'GT_ACCESS_TOKEN' 2 | export const GT_COMMENT = 'GT_COMMENT' 3 | -------------------------------------------------------------------------------- /assets/fonts/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/HEAD/assets/fonts/iconfont.eot -------------------------------------------------------------------------------- /assets/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/HEAD/assets/fonts/iconfont.ttf -------------------------------------------------------------------------------- /assets/fonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/HEAD/assets/fonts/iconfont.woff -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ## 项目版本 2 | ~ 0.0.1 3 | 4 | ## 操作系统版本 / 浏览器版本 5 | ~ 6 | 7 | ## Vue 版本 8 | ~ 9 | 10 | ## 重现链接 11 | ~ 12 | 13 | ## 重现步骤 14 | ~ 15 | 16 | ## 期待的行为 17 | ~ 18 | 19 | ## 实际的行为 20 | ~ -------------------------------------------------------------------------------- /bin/template.yml: -------------------------------------------------------------------------------- 1 | title: 2 | date: 3 | type: post 4 | category: 5 | - 6 | tag: 7 | - 8 | meta: 9 | - 10 | name: description 11 | content: 12 | - 13 | name: keywords 14 | content: 15 | author: 16 | poster: 17 | pageLayout: 18 | next: -------------------------------------------------------------------------------- /util/comment.mixin.js: -------------------------------------------------------------------------------- 1 | import Gitalk from 'gitalk' 2 | 3 | const commentMixin = { 4 | methods: { 5 | comment() { 6 | if (!this.$site.themeConfig.comment) return 7 | return new Gitalk(this.$site.themeConfig.comment) 8 | } 9 | } 10 | }; 11 | 12 | export default commentMixin -------------------------------------------------------------------------------- /layouts/Tag.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /comment/assets/icon/reply.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /comment/components/Action.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": { 3 | "tagName": "v%s", 4 | "commitMessage": "%s" 5 | }, 6 | "increment": "conventional:angular", 7 | "beforeChangelogCommand": "conventional-changelog -p angular -i CHANGELOG.md -s", 8 | "changelogCommand": "conventional-changelog -p angular | tail -n +3", 9 | "safeBump": false, 10 | "requireCleanWorkingDir": false 11 | } -------------------------------------------------------------------------------- /comment/components/Button.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /util/client.js: -------------------------------------------------------------------------------- 1 | import weeklyMap from '@dynamic/weekly' 2 | 3 | export default ({ Vue }) => { 4 | if (typeof window !== 'undefined') { 5 | const Tippy = require('v-tippy') 6 | const VueSticky = require('vue-sticky') 7 | 8 | Vue.use(Tippy) 9 | Vue.directive('sticky', VueSticky.default) 10 | } 11 | 12 | Vue.mixin({ 13 | computed: { 14 | $weeklies () { 15 | return weeklyMap 16 | } 17 | } 18 | }) 19 | } -------------------------------------------------------------------------------- /comment/components/Svg.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /comment/assets/icon/heart_on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /comment/assets/icon/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /comment/assets/icon/arrow_down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /styles/mobile.styl: -------------------------------------------------------------------------------- 1 | // narrow desktop / iPad 2 | @media (max-width: $MQNarrow) 3 | .content:not(.custom) 4 | padding 2rem 0 5 | .content-header 6 | padding 0 2rem 7 | 8 | // wide mobile 9 | @media (max-width: $MQMobile) 10 | html 11 | font-size 12px 12 | .content:not(.custom) 13 | padding 2rem 0 14 | 15 | // narrow mobile 16 | @media (max-width: $MQMobileNarrow) 17 | h1 18 | font-size 1.9rem 19 | .content:not(.custom) 20 | padding 0 21 | .content 22 | pre, pre[class*="language-"] 23 | border-radius 0 24 | -------------------------------------------------------------------------------- /components/ContentWrapper.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /comment/i18n/index.js: -------------------------------------------------------------------------------- 1 | import Polyglot from 'node-polyglot' 2 | import ZHCN from './zh-CN.json' 3 | import ZHTW from './zh-TW.json' 4 | import EN from './en.json' 5 | import ES from './es-ES.json' 6 | import FR from './fr.json' 7 | import RU from './ru.json' 8 | 9 | const i18nMap = { 10 | 'zh': ZHCN, 11 | 'zh-CN': ZHCN, 12 | 'zh-TW': ZHTW, 13 | 'en': EN, 14 | 'es-ES': ES, 15 | 'fr': FR, 16 | 'ru': RU, 17 | } 18 | 19 | export default function (language) { 20 | return new Polyglot({ 21 | phrases: i18nMap[language] || i18nMap.en, 22 | locale: language 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /comment/i18n/zh-TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "init": "Gitalk 載入中…", 3 | "no-found-related": "未找到相關的 %{link}", 4 | "please-contact": "請聯絡 %{user} 初始化評論", 5 | "init-issue": "初始化 Issue", 6 | "leave-a-comment": "寫點什麼", 7 | "preview": "預覽", 8 | "edit": "編輯", 9 | "comment": "評論", 10 | "support-markdown": "支援 Markdown 語法", 11 | "login-with-github": "使用 GitHub 登入", 12 | "first-comment-person": "成為首個留言的人吧!", 13 | "commented": "評論於", 14 | "load-more": "載入更多", 15 | "counts": "%{counts} 筆評論", 16 | "sort-asc": "從舊至新排序", 17 | "sort-desc": "從新至舊排序", 18 | "logout": "登出", 19 | "anonymous": "訪客" 20 | } 21 | -------------------------------------------------------------------------------- /comment/i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "init": "Gitalk 加载中 ...", 3 | "no-found-related": "未找到相关的 %{link} 进行评论", 4 | "please-contact": "请联系 %{user} 初始化创建", 5 | "init-issue": "初始化 Issue", 6 | "leave-a-comment": "说点什么", 7 | "preview": "预览", 8 | "edit": "编辑", 9 | "comment": "评论", 10 | "support-markdown": "支持 Markdown 语法", 11 | "login-with-github": "使用 GitHub 登录", 12 | "first-comment-person": "来做第一个留言的人吧!", 13 | "commented": "发表于", 14 | "load-more": "加载更多", 15 | "counts": "%{counts} 条评论", 16 | "sort-asc": "从旧到新排序", 17 | "sort-desc": "从新到旧排序", 18 | "logout": "注销", 19 | "anonymous": "未登录用户" 20 | } 21 | -------------------------------------------------------------------------------- /styles/arrow.styl: -------------------------------------------------------------------------------- 1 | .arrow 2 | display inline-block 3 | width 0 4 | height 0 5 | &.up 6 | border-left 4px solid transparent 7 | border-right 4px solid transparent 8 | border-bottom 6px solid $textColor 9 | &.down 10 | border-left 4px solid transparent 11 | border-right 4px solid transparent 12 | border-top 6px solid $textColor 13 | &.right 14 | border-top 4px solid transparent 15 | border-bottom 4px solid transparent 16 | border-left 6px solid $textColor 17 | &.left 18 | border-top 4px solid transparent 19 | border-bottom 4px solid transparent 20 | border-right 6px solid $textColor -------------------------------------------------------------------------------- /styles/config.styl: -------------------------------------------------------------------------------- 1 | // theme 2 | $theme = '.theme-track--' 3 | $baseFontSize= 16px 4 | 5 | // colors 6 | $accentColor = #3eaf7c 7 | $textColor = #303030 8 | $postColor = #ac3e40 9 | $viceTextColor = #6d6d6d 10 | $borderColor = #ebebeb 11 | $focusColor = #3eaf7c 12 | $codeBgColor = #282c34 13 | $bgColor = #ffffff 14 | $darkBgColor = #fafafa 15 | 16 | // layout 17 | $navbarHeight = 3.75rem 18 | $sidebarWidth = 20rem 19 | $contentWidth = 740px 20 | $footerHeight = 32px 21 | 22 | // responsive breakpoints 23 | $MQNarrow = 959px // pad 24 | $MQMobile = 719px // mobile 25 | $MQMobileNarrow = 419px 26 | 27 | // global 28 | $appWidth = 1000px 29 | $toolWidth = 16rem 30 | $pageWidth = 43rem -------------------------------------------------------------------------------- /components/DropdownTransition.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 26 | 27 | -------------------------------------------------------------------------------- /comment/assets/icon/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /layouts/Category.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /components/TagNode.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | -------------------------------------------------------------------------------- /comment/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "init": "Gitalking ...", 3 | "no-found-related": "Related %{link} not found", 4 | "please-contact": "Please contact %{user} to initialize the comment", 5 | "init-issue": "Init Issue", 6 | "leave-a-comment": "Leave a comment", 7 | "preview": "Preview", 8 | "edit": "Edit", 9 | "comment": "Comment", 10 | "support-markdown": "Markdown is supported", 11 | "login-with-github": "Login with GitHub", 12 | "first-comment-person": "Be the first guy leaving a comment!", 13 | "commented": "commented", 14 | "load-more": "Load more", 15 | "counts": "%{counts} comment |||| %{counts} comments", 16 | "sort-asc": "Sort by Oldest", 17 | "sort-desc": "Sort by Latest", 18 | "logout": "Logout", 19 | "anonymous": "Anonymous" 20 | } 21 | -------------------------------------------------------------------------------- /components/Footer.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /comment/i18n/es-ES.json: -------------------------------------------------------------------------------- 1 | { 2 | "init": "Gitalking ...", 3 | "no-found-related": "Link %{link} no encontrado", 4 | "please-contact": "Por favor contacta con %{user} para inicializar el comentario", 5 | "init-issue": "Iniciar Issue", 6 | "leave-a-comment": "Deja un comentario", 7 | "preview": "Avance", 8 | "edit": "Editar", 9 | "comment": "Comentario", 10 | "support-markdown": "Markdown es soportado", 11 | "login-with-github": "Entrar con GitHub", 12 | "first-comment-person": "Sé el primero en dejar un comentario!", 13 | "commented": "comentó", 14 | "load-more": "Cargar más", 15 | "counts": "%{counts} comentario |||| %{counts} comentarios", 16 | "sort-asc": "Ordenar por Antiguos", 17 | "sort-desc": "Ordenar por Recientes", 18 | "logout": "Salir", 19 | "anonymous": "Anónimo" 20 | } 21 | -------------------------------------------------------------------------------- /comment/i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "init": "Gitalking ...", 3 | "no-found-related": "Lien %{link} non trouvé", 4 | "please-contact": "S’il vous plaît contactez %{user} pour initialiser les commentaires", 5 | "init-issue": "Initialisation des issues", 6 | "leave-a-comment": "Laisser un commentaire", 7 | "preview": "Aperçu", 8 | "edit": "Modifier", 9 | "comment": "Commentaire", 10 | "support-markdown": "Markdown est supporté", 11 | "login-with-github": "Se connecter avec GitHub", 12 | "first-comment-person": "Être le premier à laisser un commentaire !", 13 | "commented": "commenter", 14 | "load-more": "Charger plus", 15 | "counts": "%{counts} commentaire |||| %{counts} commentaires", 16 | "sort-asc": "Trier par plus ancien", 17 | "sort-desc": "Trier par plus récent", 18 | "logout": "Déconnexion", 19 | "anonymous": "Anonyme" 20 | } 21 | -------------------------------------------------------------------------------- /components/Date.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | 26 | -------------------------------------------------------------------------------- /comment/i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "init": "Gitalking ...", 3 | "no-found-related": "Связанные %{link} не найдены", 4 | "please-contact": "Пожалуйста, свяжитесь с %{user} чтобы инициализировать комментарий", 5 | "init-issue": "Выпуск инициализации", 6 | "leave-a-comment": "Оставить комментарий", 7 | "preview": "Предварительный просмотр", 8 | "edit": "Pедактировать", 9 | "comment": "Комментарий", 10 | "support-markdown": "Поддерживается Markdown", 11 | "login-with-github": "Вход через GitHub", 12 | "first-comment-person": "Будьте первым, кто оставил комментарий", 13 | "commented": "прокомментированный", 14 | "load-more": "Загрузить ещё", 15 | "counts": "%{counts} комментарий |||| %{counts} комментарьев", 16 | "sort-asc": "Сортировать по старым", 17 | "sort-desc": "Сортировать по последним", 18 | "logout": "Выход", 19 | "anonymous": "Анонимный" 20 | } 21 | -------------------------------------------------------------------------------- /layouts/Weekly.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | -------------------------------------------------------------------------------- /layouts/Categories.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander') 4 | const figlet = require('figlet') 5 | const chalk = require('chalk') 6 | const pkg = require('../package.json') 7 | const createPost = require('./createPost') 8 | 9 | console.log( 10 | chalk.green( 11 | figlet.textSync('vp-track', { horizontalLayout: 'full' }) 12 | ) 13 | ) 14 | 15 | program.version(pkg.version) 16 | .usage(`${chalk.green('vp-track')}`) 17 | .option('-p,--post ', 'post name') 18 | .option('-d,--dest ', 'Destination folder') 19 | .option('-u,--author ', 'Github user name') 20 | .option('-t,--tag ', 'Article tags') 21 | .on('--help', () => { 22 | console.log(`${chalk.cyan('vp-track')} ${chalk.green('--post
')}`) 23 | }) 24 | .parse(process.argv) 25 | 26 | if (program.post) { 27 | createPost(program) 28 | } else { 29 | program.outputHelp() 30 | } -------------------------------------------------------------------------------- /components/List.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | 20 | 41 | -------------------------------------------------------------------------------- /components/Tag.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | 23 | -------------------------------------------------------------------------------- /components/Brand.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 28 | 29 | -------------------------------------------------------------------------------- /comment/assets/icon/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/Github.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | -------------------------------------------------------------------------------- /assets/images/GitHub.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/NavLink.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /bin/createPost.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | const ora = require('ora') 4 | const YAML = require('yamljs') 5 | 6 | function normalizeYaml(title, tags = '', author = '') { 7 | const date = new Date() 8 | const ymlPath = path.resolve(__dirname, 'template.yml') 9 | 10 | let matterObject = YAML.load(ymlPath) 11 | const result = Object.assign({}, matterObject, { 12 | date: date.toISOString().replace(/T/, ' ').replace(/\.(\w+)/, ''), 13 | author: author, 14 | tag: tags, 15 | title: title 16 | }) 17 | 18 | return YAML.stringify(result, 4, 2) 19 | } 20 | 21 | module.exports = (config) => { 22 | const title = config.post.replace(/.*\/(\w+)\.(\w+)/g, '$1') 23 | const spinner = ora('') 24 | const filePath = path.resolve(config.dest || '', config.post + '.md') 25 | const frontmatter = normalizeYaml(title, config.tag, config.author) 26 | const metaBanner = `---\n${frontmatter}---` 27 | fs.ensureFileSync(filePath) 28 | fs.writeFileSync( 29 | filePath, 30 | metaBanner 31 | ) 32 | spinner.succeed(`Create ${title} succeeded!`) 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 tomieric 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. -------------------------------------------------------------------------------- /components/SidebarButton.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /components/LayoutContainer.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 32 | 33 | -------------------------------------------------------------------------------- /components/Tab.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /comment/assets/icon/tip.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuepress-theme-track", 3 | "version": "2.0.2", 4 | "description": "vuepress-theme-track", 5 | "main": "index.js", 6 | "bin": { 7 | "vp-track": "./bin/index.js" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "release": "release-it" 12 | }, 13 | "keywords": [ 14 | "vuepress", 15 | "vuepress-theme", 16 | "vuepress-theme-track" 17 | ], 18 | "author": "tommyshao ", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@vuepress/plugin-active-header-links": "^1.7.0", 22 | "@vuepress/plugin-back-to-top": "^1.7.0", 23 | "@vuepress/plugin-google-analytics": "^1.7.0", 24 | "@vuepress/plugin-medium-zoom": "^1.7.0", 25 | "@vuepress/plugin-pwa": "^1.7.0", 26 | "@vuepress/plugin-register-components": "^1.7.0", 27 | "@yubisaki/vuepress-plugin-blog": "^0.1.8", 28 | "@yubisaki/vuepress-plugin-pagination": "^0.1.9", 29 | "chalk": "^2.4.1", 30 | "commander": "^2.19.0", 31 | "figlet": "^1.2.1", 32 | "fs-extra": "^7.0.1", 33 | "gitalk": "^1.4.1", 34 | "nprogress": "^0.2.0", 35 | "ora": "^3.0.0", 36 | "prismjs": "^1.15.0", 37 | "raw-loader": "^1.0.0", 38 | "style-resources-loader": "^1.2.1", 39 | "v-tippy": "^2.0.0", 40 | "vue-particles": "^1.0.9", 41 | "vue-popup": "^0.2.14", 42 | "vue-sticky": "^3.3.4", 43 | "vuepress-plugin-flowchart": "^1.4.3", 44 | "vuewordcloud": "^18.7.12", 45 | "yamljs": "^0.3.0" 46 | }, 47 | "devDependencies": { 48 | "conventional-changelog-cli": "^2.0.11", 49 | "release-it": "^8.0.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /util/plugin.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | module.exports = (options, ctx) => { 3 | const { 4 | pageEnhancers = [], 5 | weeklyUrl = '/weekly/' 6 | } = options 7 | 8 | return { 9 | name: 'track-plugin', 10 | 11 | enhanceAppFiles: [ 12 | path.resolve(__dirname, 'client.js') 13 | ], 14 | 15 | extendPageData (pageCtx) { 16 | const { frontmatter: rawFrontmatter } = pageCtx 17 | pageEnhancers.forEach(({ 18 | when, 19 | frontmatter = {}, 20 | data = {} 21 | }) => { 22 | if (when(pageCtx)) { 23 | Object.assign(rawFrontmatter, frontmatter) 24 | Object.assign(pageCtx, data) 25 | } 26 | }) 27 | }, 28 | 29 | ready () { 30 | const { pages } = ctx 31 | const weeklyMap = [] 32 | 33 | pages.forEach((page) => { 34 | let { key, path, frontmatter: { type, title, date } } = page; 35 | if (type === 'weekly') { 36 | weeklyMap.push({ title, path, date }) 37 | } 38 | }) 39 | 40 | ctx.weeklyMap = weeklyMap 41 | 42 | const extraPages = [ 43 | { 44 | permalink: weeklyUrl, 45 | path: weeklyUrl, 46 | frontmatter: { 47 | title: 'weekly', 48 | pageLayout: 'Weekly' 49 | } 50 | } 51 | ] 52 | 53 | extraPages.forEach(page => ctx.addPage(page)); 54 | }, 55 | 56 | async clientDynamicModules () { 57 | return [ 58 | { 59 | name: 'weekly.js', 60 | content: `export default ${JSON.stringify(ctx.weeklyMap, null, 2)}` 61 | } 62 | ] 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /components/SidebarGroup.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 39 | 40 | -------------------------------------------------------------------------------- /components/GithubCard.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 61 | 62 | -------------------------------------------------------------------------------- /components/ListContainer.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 47 | 48 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | layoutDir: './src/layouts', 5 | plugins: [ 6 | ['@vuepress/back-to-top'], 7 | ['@vuepress/medium-zoom'], 8 | ['@vuepress/google-analytics'], 9 | ['@vuepress/active-header-links'], 10 | ['@vuepress/register-components', { 11 | componentsDir: [ 12 | path.resolve(__dirname, 'components') 13 | ] 14 | }], 15 | ['@yubisaki/blog', { 16 | pageEnhancers: [ 17 | { 18 | when: ({ frontmatter }) => frontmatter.pageLayout === 'Activity', 19 | frontmatter: { layout: 'Activity' } 20 | }, 21 | { 22 | when: ({ frontmatter }) => frontmatter.pageLayout === 'Home', 23 | frontmatter: { layout: 'Home' } 24 | }, 25 | { 26 | when: ({ frontmatter }) => frontmatter.pageLayout === 'Layout', 27 | frontmatter: { layout: 'Layout' } 28 | }, 29 | { 30 | when: ({ frontmatter }) => frontmatter.pageLayout === 'Weekly', 31 | frontmatter: { layout: 'Weekly' } 32 | }, 33 | { 34 | when: ({ frontmatter }) => frontmatter.type === 'weekly', 35 | frontmatter: { layout: 'Page' } 36 | }, 37 | { 38 | when: ({ path }) => path.startsWith('/weekly'), 39 | frontmatter: { showAuthor: false, next: false, sidebar: false } 40 | } 41 | ] 42 | }], 43 | ['@yubisaki/pagination'], 44 | 'flowchart', 45 | [require('./util/plugin')] 46 | ], 47 | chainWebpack: config => { 48 | const patterns = [ 49 | path.resolve(__dirname, './styles/config.styl'), 50 | path.resolve(__dirname, './styles/mixins.styl') 51 | ] 52 | config.module.rule('stylus').oneOf('normal') 53 | .use('style-resource') 54 | .loader('style-resources-loader') 55 | .options({ patterns }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.0.2](https://github.com/tomieric/vuepress-theme-track/compare/v2.0.1...v2.0.2) (2020-10-18) 2 | 3 | 4 | 5 | ## [2.0.1](https://github.com/tomieric/vuepress-theme-track/compare/v2.0.0...v2.0.1) (2020-10-18) 6 | 7 | 8 | 9 | ## [1.0.13](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.12...v1.0.13) (2020-10-14) 10 | 11 | 12 | 13 | ## [1.0.12](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.11...v1.0.12) (2018-12-19) 14 | 15 | 16 | 17 | ## [1.0.12](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.11...v1.0.12) (2018-12-19) 18 | 19 | 20 | 21 | ## [1.0.11](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.10...v1.0.11) (2018-12-19) 22 | 23 | 24 | 25 | ## [1.0.10](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.9...v1.0.10) (2018-12-16) 26 | 27 | 28 | 29 | ## [1.0.9](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.8...v1.0.9) (2018-12-16) 30 | 31 | 32 | 33 | ## [1.0.8](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.7...v1.0.8) (2018-12-16) 34 | 35 | 36 | 37 | ## [1.0.7](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.6...v1.0.7) (2018-12-15) 38 | 39 | 40 | 41 | ## [1.0.6](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.5...v1.0.6) (2018-12-09) 42 | 43 | 44 | 45 | ## [1.0.5](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.4...v1.0.5) (2018-12-09) 46 | 47 | 48 | 49 | ## [1.0.4](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.3...v1.0.4) (2018-12-09) 50 | 51 | 52 | 53 | ## [1.0.3](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.2...v1.0.3) (2018-12-09) 54 | 55 | 56 | 57 | ## [1.0.2](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.1...v1.0.2) (2018-12-09) 58 | 59 | 60 | 61 | ## [1.0.1](https://github.com/tomieric/vuepress-theme-track/compare/v1.0.0...v1.0.1) (2018-12-09) 62 | 63 | 64 | 65 | ## 1.0.3 (2018-12-09) 66 | 67 | 68 | 69 | ## 1.0.2 (2018-12-09) 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /styles/code.styl: -------------------------------------------------------------------------------- 1 | .content 2 | code 3 | color lighten($textColor, 20%) 4 | padding 0.25rem 0.5rem 5 | margin 0 6 | font-size 0.85em 7 | background-color rgba(27,31,35,0.05) 8 | border-radius 3px 9 | 10 | .content 11 | pre, pre[class*="language-"] 12 | background-color $codeBgColor 13 | line-height 1.4 14 | border-radius 6px 15 | padding 1.25rem 1.5rem 16 | margin 0.85rem 0 17 | white-space pre-wrap 18 | word-break break-word 19 | overflow auto 20 | position relative 21 | code 22 | color #fff 23 | padding 0 24 | background-color none 25 | border-radius 0 26 | &:before 27 | position absolute 28 | top 0.8em 29 | right 1em 30 | font-size 0.75rem 31 | color rgba(255, 255, 255, 0.4) 32 | .highlighted-line 33 | background-color rgba(0, 0, 0, 66%) 34 | display block 35 | margin 0.1rem -1.5rem 0 36 | padding 0.1rem 1.8rem 37 | 38 | pre[class="language-js"], pre[class="language-javascript"] 39 | &:before 40 | content "js" 41 | 42 | pre[class="language-html"], pre[class="language-markup"] 43 | &:before 44 | content "html" 45 | 46 | pre[class="language-markdown"], pre[class="language-md"] 47 | &:before 48 | content "md" 49 | 50 | pre[class="language-vue"]:before 51 | content "vue" 52 | 53 | pre[class="language-css"]:before 54 | content "css" 55 | 56 | pre[class="language-sass"]:before 57 | content "sass" 58 | 59 | pre[class="language-less"]:before 60 | content "less" 61 | 62 | pre[class="language-scss"]:before 63 | content "scss" 64 | 65 | pre[class="language-stylus"]:before 66 | content "stylus" 67 | 68 | pre[class="language-json"]:before 69 | content "json" 70 | 71 | pre[class="language-ruby"]:before 72 | content "rb" 73 | 74 | pre[class="language-python"]:before 75 | content "py" 76 | 77 | pre[class="language-go"]:before 78 | content "go" 79 | 80 | pre[class="language-java"]:before 81 | content "java" 82 | 83 | pre[class="language-c"]:before 84 | content "c" 85 | 86 | pre[class="language-bash"]:before 87 | content "sh" -------------------------------------------------------------------------------- /layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 49 | 50 | -------------------------------------------------------------------------------- /comment/util.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export const queryParse = (search) => { 4 | if (typeof window === 'undefined') return {} 5 | search = search || window.location.search 6 | if (!search) return {} 7 | 8 | const queryString = search[0] === '?' ? search.substring(1) : search 9 | const query = {} 10 | queryString 11 | .split('&') 12 | .forEach(queryStr => { 13 | const [key, value] = queryStr.split('=') 14 | /* istanbul ignore else */ 15 | if (key) query[decodeURIComponent(key)] = decodeURIComponent(value) 16 | }) 17 | 18 | return query 19 | } 20 | 21 | export const queryStringify = query => { 22 | const queryString = Object.keys(query) 23 | .map(key => `${key}=${encodeURIComponent(query[key] || '')}`) 24 | .join('&') 25 | return queryString 26 | } 27 | 28 | export const axiosJSON = axios.create({ 29 | headers: { 30 | 'Accept': 'application/json' 31 | } 32 | }) 33 | 34 | export const axiosGithub = axios.create({ 35 | baseURL: 'https://api.github.com', 36 | headers: { 37 | 'Accept': 'application/json' 38 | } 39 | }) 40 | 41 | export const getMetaContent = (name, content) => { 42 | /* istanbul ignore next */ 43 | content || (content = 'content') 44 | /* istanbul ignore next */ 45 | const el = document.querySelector(`meta[name='${name}']`) 46 | /* istanbul ignore next */ 47 | return el && el.getAttribute(content) 48 | } 49 | 50 | export const formatErrorMsg = err => { 51 | let msg = 'Error: ' 52 | if (err.response && err.response.data && err.response.data.message) { 53 | msg += `${err.response.data.message}. ` 54 | err.response.data.errors && (msg += err.response.data.errors.map(e => e.message).join(', ')) 55 | } else { 56 | msg += err.message 57 | } 58 | return msg 59 | } 60 | 61 | export const hasClassInParent = (element, ...className) => { 62 | /* istanbul ignore next */ 63 | let yes = false 64 | /* istanbul ignore next */ 65 | if (typeof element.className === 'undefined') return false 66 | /* istanbul ignore next */ 67 | const classes = element.className.split(' ') 68 | /* istanbul ignore next */ 69 | className.forEach((c, i) => { 70 | /* istanbul ignore next */ 71 | yes = yes || (classes.indexOf(c) >= 0) 72 | }) 73 | /* istanbul ignore next */ 74 | if (yes) return yes 75 | /* istanbul ignore next */ 76 | return element.parentNode && hasClassInParent(element.parentNode, className) 77 | } 78 | -------------------------------------------------------------------------------- /components/Pagination.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 45 | 46 | -------------------------------------------------------------------------------- /layouts/404.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 57 | 58 | 88 | -------------------------------------------------------------------------------- /components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 70 | 71 | -------------------------------------------------------------------------------- /components/SidebarLink.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | -------------------------------------------------------------------------------- /styles/custom-blocks.styl: -------------------------------------------------------------------------------- 1 | .content 2 | code 3 | color lighten($textColor, 20%) 4 | padding 0.25rem 0.5rem 5 | margin 0 6 | font-size 0.85em 7 | background-color rgba(27,31,35,0.05) 8 | border-radius 3px 9 | .vuepress-flowchart 10 | overflow auto 11 | 12 | .content 13 | pre, pre[class*="language-"] 14 | background-color $codeBgColor 15 | line-height 1.4 16 | border-radius 6px 17 | padding 1.25rem 1.5rem 18 | margin 0.85rem 0 19 | white-space pre-wrap 20 | word-break break-word 21 | overflow auto 22 | position relative 23 | code 24 | color #fff 25 | padding 0 26 | background-color none 27 | border-radius 0 28 | &:before 29 | position absolute 30 | top 0.8em 31 | right 1em 32 | font-size 0.75rem 33 | color rgba(255, 255, 255, 0.4) 34 | .highlighted-line 35 | background-color rgba(0, 0, 0, 66%) 36 | display block 37 | margin 0.1rem -1.5rem 0 38 | padding 0.1rem 1.8rem 39 | 40 | pre[class="language-js"], pre[class="language-javascript"] 41 | &:before 42 | content "js" 43 | 44 | pre[class="language-html"], pre[class="language-markup"] 45 | &:before 46 | content "html" 47 | 48 | pre[class="language-markdown"], pre[class="language-md"] 49 | &:before 50 | content "md" 51 | 52 | pre[class="language-vue"]:before 53 | content "vue" 54 | 55 | pre[class="language-css"]:before 56 | content "css" 57 | 58 | pre[class="language-sass"]:before 59 | content "sass" 60 | 61 | pre[class="language-less"]:before 62 | content "less" 63 | 64 | pre[class="language-scss"]:before 65 | content "scss" 66 | 67 | pre[class="language-stylus"]:before 68 | content "stylus" 69 | 70 | pre[class="language-json"]:before 71 | content "json" 72 | 73 | pre[class="language-ruby"]:before 74 | content "rb" 75 | 76 | pre[class="language-python"]:before 77 | content "py" 78 | 79 | pre[class="language-go"]:before 80 | content "go" 81 | 82 | pre[class="language-java"]:before 83 | content "java" 84 | 85 | pre[class="language-c"]:before 86 | content "c" 87 | 88 | pre[class="language-bash"]:before 89 | content "sh" 90 | 91 | .custom-block 92 | .custom-block-title 93 | font-weight 600 94 | margin-bottom -0.4rem 95 | &.tip, &.warning, &.danger 96 | padding .1rem 1.5rem 97 | border-left-width .5rem 98 | border-left-style solid 99 | margin 1rem 0 100 | &.tip 101 | background-color #f3f5f7 102 | border-color #42b983 103 | &.warning 104 | background-color rgba(255,229,100,.3) 105 | border-color darken(#ffe564, 35%) 106 | color darken(#ffe564, 70%) 107 | .custom-block-title 108 | color darken(#ffe564, 50%) 109 | a 110 | color $textColor 111 | &.danger 112 | background-color #ffe6e6 113 | border-color darken(red, 20%) 114 | color darken(red, 70%) 115 | .custom-block-title 116 | color darken(red, 40%) 117 | a 118 | color $textColor -------------------------------------------------------------------------------- /styles/theme.styl: -------------------------------------------------------------------------------- 1 | @require './config' 2 | @require './mixins' 3 | @require './arrow' 4 | @require './custom-blocks' 5 | @require './code' 6 | 7 | html, body 8 | padding 0 9 | margin 0 10 | 11 | body 12 | font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif 13 | -webkit-font-smoothing antialiased 14 | -moz-osx-font-smoothing grayscale 15 | font-size $baseFontSize 16 | color $textColor 17 | background $bgColor 18 | 19 | .content:not(.custom) 20 | max-width $contentWidth 21 | margin 0 auto 22 | padding 2rem 2.5rem 23 | width 100% 24 | > *:first-child 25 | margin-top $navbarHeight - 2rem 26 | a:hover 27 | text-decoration underline 28 | p.demo 29 | padding 1rem 1.5rem 30 | border 1px solid #ddd 31 | border-radius 4px 32 | img 33 | max-width 100% 34 | 35 | .content-header 36 | max-width $contentWidth 37 | margin 0 auto 38 | padding 0 2rem 39 | 40 | .content.custom 41 | padding 0 42 | margin 0 43 | 44 | a 45 | font-weight 500 46 | color $accentColor 47 | text-decoration none 48 | 49 | p a code 50 | font-weight 400 51 | color $accentColor 52 | 53 | kbd 54 | background #eee 55 | border solid 0.15rem #ddd 56 | border-bottom solid 0.25rem #ddd 57 | border-radius 0.15rem 58 | padding 0 0.15em 59 | 60 | blockquote 61 | font-size 1rem 62 | color #999 63 | border-left .25rem solid #dfe2e5 64 | margin-left 0 65 | padding-left 1rem 66 | 67 | ul, ol 68 | padding-left 1.2em 69 | 70 | strong 71 | font-weight 600 72 | 73 | h1, h2, h3, h4, h5, h6 74 | font-weight 600 75 | line-height 1.25 76 | .content:not(.custom) > & 77 | margin-top (0.5rem - $navbarHeight) 78 | padding-top ($navbarHeight + 1rem) 79 | margin-bottom 0 80 | &:first-child 81 | margin-top -3.5rem 82 | margin-bottom 1rem 83 | + p, + pre, + .custom-block 84 | margin-top 2rem 85 | &:hover .header-anchor 86 | opacity: 1 87 | 88 | h1 89 | font-size 2rem 90 | 91 | h2 92 | font-size 1.5rem 93 | padding-bottom .3rem 94 | border-bottom 1px solid $borderColor 95 | 96 | h3 97 | font-size 1.2rem 98 | 99 | a.header-anchor 100 | font-size 0.85em 101 | float left 102 | margin-left -0.87em 103 | padding-right 0.23em 104 | margin-top 0.125em 105 | opacity 0 106 | &:hover 107 | text-decoration none 108 | 109 | code, kbd 110 | font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace 111 | 112 | p, ul, ol 113 | line-height 1.7 114 | 115 | hr 116 | border 0 117 | border-top 1px solid $borderColor 118 | 119 | table 120 | border-collapse collapse 121 | margin 1rem 0 122 | 123 | tr 124 | border-top 1px solid #dfe2e5 125 | &:nth-child(2n) 126 | background-color #f6f8fa 127 | 128 | th, td 129 | border 1px solid #dfe2e5 130 | padding .6em 1em 131 | 132 | 133 | /* 134 | * Global Custom styles 135 | **/ 136 | .theme-track--page-poster 137 | display block 138 | width 100% 139 | height auto 140 | background $darkBgColor -------------------------------------------------------------------------------- /assets/fonts/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1543128353203'); /* IE9*/ 4 | src: url('iconfont.eot?t=1543128353203#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAbYAAsAAAAACdgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8dkiFY21hcAAAAYAAAABzAAABwJr+nGtnbHlmAAAB9AAAAsIAAANctkwLaGhlYWQAAAS4AAAAMQAAADYTWxBVaGhlYQAABOwAAAAgAAAAJAfZA4tobXR4AAAFDAAAABQAAAAYGAT/+2xvY2EAAAUgAAAADgAAAA4DogJqbWF4cAAABTAAAAAfAAAAIAEZALBuYW1lAAAFUAAAAUUAAAJtPlT+fXBvc3QAAAaYAAAAPQAAAE6AuJuceJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWScwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMTybxNzwv4EhhrmBoQEozAiSAwDoTgx2eJztkdENwyAMRJ8JVBXKKJmC/naUqF+dJEN6jfSMky166KGzZYF0BhqwiE1UsC9G6KOuzf5Cn/3KUN15UiiOv3z34zxBftz+kmmus+qEL3qjxk/24K913u+rapFfEnn7SJSZEk5iP74nsSM/EtoPmn4ciQB4nEVSz2sUMRTOSzLJZCfNdn4v2921O6s72NpRZ2d3D8VWoVUqotYfFPEgCF5E8OJJqPbixYJaFFGhInjw4qEFEUTB+hfoSf+AHnv1IurUzEo1kC/fy/te3nu8ILH9c/sx2SZLiCKB7qC76D56hJ6i5+glQnuy1IviAty0gX1PYc7acQL7IUvIFO1m7bgd9/q9BBhnPMFRcRQCBWEQMq7A9xq4oGVgdeAKR0VwQrQnbPBO2usXj8btVkK62RQmQRjw+F/ScMD0zgYMDjnOmOtuXX2/PJ9eWJyTNYIJAC8J2kwnO9FwyVJMX4XjlZHGcDTRT73Aa4xUJnwDgCrLKrvNMT87YrISpSUvDJqR8pjnDxPGTFq2FZO1JK7WODGVxPT44sXO/PKHCsWG5TnOW6VmXDc2y47j9IXjCpfMzS+/v3r2ya1LVYxtuTeYFfVqYBhqZN/h5OhJN+DNWjguy9aQZwjGKDUcaSkxVqm3yr7bbe4/lu22RqgwFSfco5xWdinfMCkAkQRb5eFSa3cd4BQrW4xC9dLtJ+eK1n+taAF0Su51NnRUeHNKRcJFgPTCa/gN4gjBQeACQsAv8o1ROJB/GYXTcAMOj+ZfBlxL/+sNbcQC+oDXRvM1OK1F+JFm+ZoO1b8CbX8iH8k0aqEYjaPJQq0HPw6RnmjcjjgwL0inwfaCTjPtde0wSHsZdPyYtOyOYQuwW/ZfvW+3yLtVS9w05TXDMfLzTNpsQZr4sikXmC3zzTNnTrx+nd+DGtRWpfnVlKtwb/WbsCzRvcZYft5wLLaQS1NKE74vMMuBK8/mf8/C7Hd4C/V88/cPuFCo81dFe0TX/pmskwPIQqGuH0ECsQLegHAK+ppoU5MGkHbWS3dB4LEoJjNkY2VlgwzwFN1aX9+iA3wo3GppqQB4sOMvcMevMa+Uqq5YKkCn/wMxUZ7/AAB4nGNgZGBgAOJPB7M74vltvjJwszCAwA2FNEUY/f/3/2ksDMwNQC4HAxNIFABGxgt1AAAAeJxjYGRgYG7438AQw8Ly/zcDAwsDA1AEBbABAHXlBG14nGNhYGBgYfn/mwVEI2EAJE0CFwAAAAAA5AD8ARABbgGuAAB4nGNgZGBgYGNYwsDJAAJMQMwFhAwM/8F8BgAZvwHKAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nGNgYoAALgbsgI2RiZGZkYWRlZGNkZ2BLT2zJKM0iSUvtaKEpaAotYwlIz83la04NbEoOYOBAQCzOApqAAAA') format('woff'), 6 | url('iconfont.ttf?t=1543128353203') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1543128353203#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-github:before { content: "\e600"; } 19 | 20 | .icon-next:before { content: "\e642"; } 21 | 22 | .icon-prev:before { content: "\e643"; } 23 | 24 | .icon-home:before { content: "\e66c"; } 25 | 26 | .icon-search:before { content: "\e692"; } 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vuepress-theme-track 2 | 3 | * [true-track](https://www.ui.cn/detail/120714.html) 4 | * [vuepress-theme-yubisaki](https://github.com/Yubisaki/vuepress-theme-yubisaki) 5 | 6 | ![HOME](./screenshot/home.jpg) 7 | ![POST](./screenshot/post.jpg) 8 | 9 | [more screenshot](./screenshot/home.jpg) 10 | 11 | [Live Demo](https://kuaizi-co.github.io/blog/track.html) 12 | 13 | ## USAGE 14 | 15 | ``` 16 | $ yarn add vuepress-theme-track 17 | # or 18 | $ npm install vuepress-theme-track --save-dev 19 | ``` 20 | ### Configuration 21 | 22 | In your `docs/.vuepress/config.js` file, It's to add this: 23 | 24 | ``` 25 | module.exports = { 26 | title: '前端周刊', 27 | description: '每周分享前端知识', 28 | base: '/blog/', 29 | head: [ 30 | ['link', { rel: 'icon', href: 'favicon.ico' }] 31 | ], 32 | // use vuepress-theme-track 33 | theme: 'track', 34 | // local development 35 | port: 3000, 36 | // Google Analytics ID 37 | ga: '', 38 | // fuck IE 39 | evergreen: true, 40 | markdown: { 41 | // markdown-it-anchor 的选项 42 | anchor: { permalink: true }, 43 | // markdown-it-toc 的选项 44 | toc: { includeLevel: [1, 2] }, 45 | config: md => { 46 | md.use(require('markdown-it-task-lists')) // 一个 checkbox 的 TODO List 插件 47 | .use(require('markdown-it-imsize'), { autofill: true }) // 支持自定义 md 图片大小 ![](http://test.png =200x200) 48 | } 49 | }, 50 | themeConfig: { 51 | footer: 'MIT Licensed | Copyright © 2018-present tommyshao', 52 | // github card 53 | // github account name 54 | github: 'tomieric', 55 | // logo 56 | logo: '/images/logo.png', 57 | // homepage logo 58 | logoInverse: '/images/logo-white.png', 59 | // It's show font color to post title 60 | accentColor: '#ac3e40', 61 | // pageSize 62 | per_page: 5, 63 | // date format 64 | date_format: 'yyyy-MM-dd', 65 | // Tag 66 | tags: true, 67 | // gitalk 68 | // https://github.com/Yubisaki/vuepress-theme-yubisaki#comment-system 69 | comment: { 70 | clientID: '', 71 | clientSecret: '', 72 | repo: 'https://github.com/tomieric/vuepress-theme-track.git', 73 | owner: 'tomieric', 74 | admin: ['tomieric'], 75 | perPage: 5, 76 | // id: 'comment', // Ensure uniqueness and length less than 50 77 | distractionFreeMode: false // Facebook-like distraction free mode 78 | }, 79 | // navLinks 80 | nav: [ 81 | { text: '首页', link: '/'}, // home 82 | { text: '周刊', link: '/weekly/'}, // blog 83 | { text: '分类', link: '/category/' }, // category 84 | { text: '标签', link: '/tag/' }, // tag 85 | { text: '关于我们', link: '/about' }, 86 | { text: 'Track', link: '/track' } 87 | ], 88 | // page list home url 89 | pageRoot: '/weekly/', 90 | sidebar: 'auto', 91 | // show author in post article, Default: false 92 | // Team blog, show everyone in memenbers 93 | showAuthor: true 94 | } 95 | } 96 | ``` 97 | 98 | ### Scripts 99 | 100 | In your `package.json` file 101 | 102 | ``` 103 | # package.json 104 | { 105 | "scripts": { 106 | "docs:dev": "vuepress dev docs", 107 | "docs:build": "vuepress build docs", 108 | "docs:deploy": "gh-pages -d ./vuepress/dist" 109 | } 110 | } 111 | ``` 112 | 113 | ### CommandLine 114 | 115 | we're support to the `vp-track` command,It's usually use to create a new post 116 | 117 | ``` 118 | $ npx vp-track --help 119 | # -p post 120 | # -d destination 121 | $ npx vp-track -p test -d docs/weekly 122 | > ✔ Create test succeeded! 123 | ``` -------------------------------------------------------------------------------- /layouts/Activity.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 64 | 65 | -------------------------------------------------------------------------------- /components/Article.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 71 | 72 | -------------------------------------------------------------------------------- /comment/graphql/getComments.js: -------------------------------------------------------------------------------- 1 | import { axiosGithub } from "../util"; 2 | 3 | const getQL = (vars, pagerDirection) => { 4 | const cursorDirection = pagerDirection === "last" ? "before" : "after"; 5 | const ql = ` 6 | query getIssueAndComments( 7 | $owner: String!, 8 | $repo: String!, 9 | $id: Int!, 10 | $cursor: String, 11 | $pageSize: Int! 12 | ) { 13 | repository(owner: $owner, name: $repo) { 14 | issue(number: $id) { 15 | title 16 | url 17 | bodyHTML 18 | createdAt 19 | comments(${pagerDirection}: $pageSize, ${cursorDirection}: $cursor) { 20 | totalCount 21 | pageInfo { 22 | ${pagerDirection === "last" ? "hasPreviousPage" : "hasNextPage"} 23 | ${cursorDirection === "before" ? "startCursor" : "endCursor"} 24 | } 25 | nodes { 26 | id 27 | databaseId 28 | author { 29 | avatarUrl 30 | login 31 | url 32 | } 33 | bodyHTML 34 | body 35 | createdAt 36 | reactions(first: 100, content: HEART) { 37 | totalCount 38 | viewerHasReacted 39 | pageInfo{ 40 | hasNextPage 41 | } 42 | nodes { 43 | id 44 | databaseId 45 | user { 46 | login 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | `; 56 | 57 | if (vars.cursor === null) delete vars.cursor; 58 | 59 | return { 60 | operationName: "getIssueAndComments", 61 | query: ql, 62 | variables: vars 63 | }; 64 | }; 65 | 66 | function getComments(issue) { 67 | const { 68 | owner, 69 | repo, 70 | perPage, 71 | pagerDirection, 72 | defaultAuthor 73 | } = this.options; 74 | return axiosGithub 75 | .post( 76 | "/graphql", 77 | getQL( 78 | { 79 | owner, 80 | repo, 81 | id: issue.number, 82 | pageSize: perPage, 83 | cursor: this.cursor 84 | }, 85 | pagerDirection 86 | ), 87 | { 88 | headers: { 89 | Authorization: `bearer ${this.accessToken}` 90 | } 91 | } 92 | ) 93 | .then(res => { 94 | const data = res.data.data.repository.issue.comments; 95 | const items = data.nodes.map(node => { 96 | const author = node.author || defaultAuthor; 97 | 98 | return { 99 | id: node.databaseId, 100 | gId: node.id, 101 | user: { 102 | avatar_url: author.avatarUrl, 103 | login: author.login, 104 | html_url: author.url 105 | }, 106 | created_at: node.createdAt, 107 | body_html: node.bodyHTML, 108 | body: node.body, 109 | html_url: `https://github.com/${owner}/${repo}/issues/${issue.number}#issuecomment-${node.databaseId}`, 110 | reactions: node.reactions 111 | }; 112 | }); 113 | 114 | let cs; 115 | 116 | if (pagerDirection === "last") { 117 | cs = [...items, ...this.comments]; 118 | } else { 119 | cs = [...this.comments, ...items]; 120 | } 121 | 122 | const isLoadOver = 123 | data.pageInfo.hasPreviousPage === false || 124 | data.pageInfo.hasNextPage === false; 125 | this.comments = cs; 126 | this.isLoadOver = isLoadOver; 127 | this.cursor = data.pageInfo.startCursor || data.pageInfo.endCursor; 128 | 129 | return cs; 130 | }); 131 | } 132 | 133 | export default getComments; 134 | -------------------------------------------------------------------------------- /comment/components/Comment.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 166 | -------------------------------------------------------------------------------- /components/NavLinks.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 92 | 93 | -------------------------------------------------------------------------------- /assets/fonts/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /components/PopOver.vue: -------------------------------------------------------------------------------- 1 | 18 | 110 | -------------------------------------------------------------------------------- /assets/fonts/iconfont.js: -------------------------------------------------------------------------------- 1 | !function(a){var t,c='',e=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss");if(e&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}!function(t){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(t,0);else{var e=function(){document.removeEventListener("DOMContentLoaded",e,!1),t()};document.addEventListener("DOMContentLoaded",e,!1)}else document.attachEvent&&(n=t,o=a.document,i=!1,l=function(){i||(i=!0,n())},(c=function(){try{o.documentElement.doScroll("left")}catch(t){return void setTimeout(c,50)}l()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,l())});var n,o,i,l,c}(function(){var t,e,n,o,i,l;(t=document.createElement("div")).innerHTML=c,c=null,(e=t.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",n=e,(o=document.body).firstChild?(i=n,(l=o.firstChild).parentNode.insertBefore(i,l)):o.appendChild(n))})}(window); -------------------------------------------------------------------------------- /components/SearchBox.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 164 | 165 | -------------------------------------------------------------------------------- /components/Tabs.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 207 | 208 | -------------------------------------------------------------------------------- /util/index.js: -------------------------------------------------------------------------------- 1 | export const hashRE = /#.*$/ 2 | export const extRE = /\.(md|html)$/ 3 | export const endingSlashRE = /\/$/ 4 | export const outboundRE = /^(https?:|mailto:|tel:)/ 5 | 6 | export function normalize (path) { 7 | return decodeURI(path) 8 | .replace(hashRE, '') 9 | .replace(extRE, '') 10 | } 11 | 12 | export function getHash (path) { 13 | const match = path.match(hashRE) 14 | if (match) { 15 | return match[0] 16 | } 17 | } 18 | 19 | export function isExternal (path) { 20 | return outboundRE.test(path) 21 | } 22 | 23 | export function isMailto (path) { 24 | return /^mailto:/.test(path) 25 | } 26 | 27 | export function isTel (path) { 28 | return /^tel:/.test(path) 29 | } 30 | 31 | export function ensureExt (path) { 32 | if (isExternal(path)) { 33 | return path 34 | } 35 | const hashMatch = path.match(hashRE) 36 | const hash = hashMatch ? hashMatch[0] : '' 37 | const normalized = normalize(path) 38 | 39 | if (endingSlashRE.test(normalized)) { 40 | return path 41 | } 42 | return normalized + '.html' + hash 43 | } 44 | 45 | export function isActive (route, path) { 46 | const routeHash = route.hash 47 | const linkHash = getHash(path) 48 | if (linkHash && routeHash !== linkHash) { 49 | return false 50 | } 51 | const routePath = normalize(route.path) 52 | const pagePath = normalize(path) 53 | return routePath === pagePath 54 | } 55 | 56 | export function resolvePage (pages, rawPath, base) { 57 | if (base) { 58 | rawPath = resolvePath(rawPath, base) 59 | } 60 | const path = normalize(rawPath) 61 | for (let i = 0; i < pages.length; i++) { 62 | if (normalize(pages[i].regularPath) === path) { 63 | return Object.assign({}, pages[i], { 64 | type: 'page', 65 | path: ensureExt(pages[i].path) 66 | }) 67 | } 68 | } 69 | console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`) 70 | return {} 71 | } 72 | 73 | function resolvePath (relative, base, append) { 74 | const firstChar = relative.charAt(0) 75 | if (firstChar === '/') { 76 | return relative 77 | } 78 | 79 | if (firstChar === '?' || firstChar === '#') { 80 | return base + relative 81 | } 82 | 83 | const stack = base.split('/') 84 | 85 | // remove trailing segment if: 86 | // - not appending 87 | // - appending to trailing slash (last segment is empty) 88 | if (!append || !stack[stack.length - 1]) { 89 | stack.pop() 90 | } 91 | 92 | // resolve relative path 93 | const segments = relative.replace(/^\//, '').split('/') 94 | for (let i = 0; i < segments.length; i++) { 95 | const segment = segments[i] 96 | if (segment === '..') { 97 | stack.pop() 98 | } else if (segment !== '.') { 99 | stack.push(segment) 100 | } 101 | } 102 | 103 | // ensure leading slash 104 | if (stack[0] !== '') { 105 | stack.unshift('') 106 | } 107 | 108 | return stack.join('/') 109 | } 110 | 111 | /** 112 | * @param { Page } page 113 | * @param { string } regularPath 114 | * @param { SiteData } site 115 | * @param { string } localePath 116 | * @returns { SidebarGroup } 117 | */ 118 | export function resolveSidebarItems (page, regularPath, site, localePath) { 119 | const { pages, themeConfig } = site 120 | 121 | const localeConfig = localePath && themeConfig.locales 122 | ? themeConfig.locales[localePath] || themeConfig 123 | : themeConfig 124 | 125 | const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar 126 | 127 | if (pageSidebarConfig === 'auto') { 128 | return resolveHeaders(page) 129 | } 130 | 131 | const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar 132 | if (!sidebarConfig) { 133 | return [] 134 | } else { 135 | const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig) 136 | return config 137 | ? config.map(item => resolveItem(item, pages, base)) 138 | : [] 139 | } 140 | } 141 | 142 | /** 143 | * @param { Page } page 144 | * @returns { SidebarGroup } 145 | */ 146 | function resolveHeaders (page) { 147 | const headers = groupHeaders(page.headers || []) 148 | return [{ 149 | type: 'group', 150 | collapsable: false, 151 | title: page.title, 152 | children: headers.map(h => ({ 153 | type: 'auto', 154 | title: h.title, 155 | basePath: page.path, 156 | path: page.path + '#' + h.slug, 157 | children: h.children || [] 158 | })) 159 | }] 160 | } 161 | 162 | export function groupHeaders (headers) { 163 | // group h3s under h2 164 | headers = headers.map(h => Object.assign({}, h)) 165 | let lastH2 166 | headers.forEach(h => { 167 | if (h.level === 2) { 168 | lastH2 = h 169 | } else if (lastH2) { 170 | (lastH2.children || (lastH2.children = [])).push(h) 171 | } 172 | }) 173 | return headers.filter(h => h.level === 2) 174 | } 175 | 176 | export function resolveNavLinkItem (linkItem) { 177 | return Object.assign(linkItem, { 178 | type: linkItem.items && linkItem.items.length ? 'links' : 'link' 179 | }) 180 | } 181 | 182 | /** 183 | * @param { Route } route 184 | * @param { Array | Array | [link: string]: SidebarConfig } config 185 | * @returns { base: string, config: SidebarConfig } 186 | */ 187 | export function resolveMatchingConfig (regularPath, config) { 188 | if (Array.isArray(config)) { 189 | return { 190 | base: '/', 191 | config: config 192 | } 193 | } 194 | for (const base in config) { 195 | if (ensureEndingSlash(regularPath).indexOf(base) === 0) { 196 | return { 197 | base, 198 | config: config[base] 199 | } 200 | } 201 | } 202 | return {} 203 | } 204 | 205 | function ensureEndingSlash (path) { 206 | return /(\.html|\/)$/.test(path) 207 | ? path 208 | : path + '/' 209 | } 210 | 211 | function resolveItem (item, pages, base, isNested) { 212 | if (typeof item === 'string') { 213 | return resolvePage(pages, item, base) 214 | } else if (Array.isArray(item)) { 215 | return Object.assign(resolvePage(pages, item[0], base), { 216 | title: item[1] 217 | }) 218 | } else { 219 | if (isNested) { 220 | console.error( 221 | '[vuepress] Nested sidebar groups are not supported. ' + 222 | 'Consider using navbar + categories instead.' 223 | ) 224 | } 225 | const children = item.children || [] 226 | return { 227 | type: 'group', 228 | title: item.title, 229 | children: children.map(child => resolveItem(child, pages, base, true)), 230 | collapsable: item.collapsable !== false 231 | } 232 | } 233 | } 234 | 235 | export const debounce = function (func, wait, immediate) { 236 | var timeout, args, context, timestamp, result; 237 | if (null == wait) wait = 100; 238 | 239 | function later() { 240 | var last = Date.now() - timestamp; 241 | 242 | if (last < wait && last >= 0) { 243 | timeout = setTimeout(later, wait - last); 244 | } else { 245 | timeout = null; 246 | if (!immediate) { 247 | result = func.apply(context, args); 248 | context = args = null; 249 | } 250 | } 251 | }; 252 | 253 | var debounced = function(){ 254 | context = this; 255 | args = arguments; 256 | timestamp = Date.now(); 257 | var callNow = immediate && !timeout; 258 | if (!timeout) timeout = setTimeout(later, wait); 259 | if (callNow) { 260 | result = func.apply(context, args); 261 | context = args = null; 262 | } 263 | 264 | return result; 265 | }; 266 | 267 | debounced.clear = function() { 268 | if (timeout) { 269 | clearTimeout(timeout); 270 | timeout = null; 271 | } 272 | }; 273 | 274 | debounced.flush = function() { 275 | if (timeout) { 276 | result = func.apply(context, args); 277 | context = args = null; 278 | 279 | clearTimeout(timeout); 280 | timeout = null; 281 | } 282 | }; 283 | 284 | return debounced; 285 | } -------------------------------------------------------------------------------- /layouts/Tags.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 69 | 70 | -------------------------------------------------------------------------------- /layouts/Page.vue: -------------------------------------------------------------------------------- 1 | 98 | 99 | 200 | 201 | -------------------------------------------------------------------------------- /layouts/Home.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 128 | 129 | -------------------------------------------------------------------------------- /comment/index.vue: -------------------------------------------------------------------------------- 1 |