├── .github └── issue_template.md ├── .gitignore ├── .npmignore ├── .release-it.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── fonts │ ├── iconfont.css │ ├── iconfont.eot │ ├── iconfont.js │ ├── iconfont.svg │ ├── iconfont.ttf │ └── iconfont.woff └── images │ ├── 404.jpg │ ├── GitHub.svg │ └── world.png ├── bin ├── createPost.js ├── index.js └── template.yml ├── comment ├── assets │ └── icon │ │ ├── arrow_down.svg │ │ ├── edit.svg │ │ ├── github.svg │ │ ├── heart.svg │ │ ├── heart_on.svg │ │ ├── reply.svg │ │ └── tip.svg ├── components │ ├── Action.vue │ ├── Button.vue │ ├── Comment.vue │ └── Svg.vue ├── const.js ├── graphql │ └── getComments.js ├── i18n │ ├── en.json │ ├── es-ES.json │ ├── fr.json │ ├── index.js │ ├── ru.json │ ├── zh-CN.json │ └── zh-TW.json ├── index.styl ├── index.vue ├── mixin │ └── index.js └── util.js ├── components ├── Article.vue ├── Brand.vue ├── ContentWrapper.vue ├── Date.vue ├── DropdownTransition.vue ├── Footer.vue ├── Github.vue ├── GithubCard.vue ├── LayoutContainer.vue ├── List.vue ├── ListContainer.vue ├── NavBar.vue ├── NavLink.vue ├── NavLinks.vue ├── Pagination.vue ├── PopOver.vue ├── SearchBox.vue ├── Sidebar.vue ├── SidebarButton.vue ├── SidebarGroup.vue ├── SidebarLink.vue ├── Tab.vue ├── Tabs.vue ├── Tag.vue └── TagNode.vue ├── index.js ├── layouts ├── 404.vue ├── Activity.vue ├── Categories.vue ├── Category.vue ├── Home.vue ├── Layout.vue ├── Page.vue ├── Tag.vue ├── Tags.vue └── Weekly.vue ├── package.json ├── screenshot ├── home.jpg ├── page.jpg ├── post.jpg └── tag.jpg ├── styles ├── arrow.styl ├── code.styl ├── config.styl ├── custom-blocks.styl ├── mixins.styl ├── mobile.styl └── theme.styl └── util ├── client.js ├── comment.mixin.js ├── index.js └── plugin.js /.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 | ~ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.lock 2 | node_modules 3 | .vscode 4 | package-lock.json 5 | yarn.lock -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs 3 | screenshot -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /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,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') 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 | -------------------------------------------------------------------------------- /assets/fonts/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/298bbe8939aa96dec118c703ef28466bd5b93b2a/assets/fonts/iconfont.eot -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/298bbe8939aa96dec118c703ef28466bd5b93b2a/assets/fonts/iconfont.ttf -------------------------------------------------------------------------------- /assets/fonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/298bbe8939aa96dec118c703ef28466bd5b93b2a/assets/fonts/iconfont.woff -------------------------------------------------------------------------------- /assets/images/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/298bbe8939aa96dec118c703ef28466bd5b93b2a/assets/images/404.jpg -------------------------------------------------------------------------------- /assets/images/GitHub.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomieric/vuepress-theme-track/298bbe8939aa96dec118c703ef28466bd5b93b2a/assets/images/world.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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: -------------------------------------------------------------------------------- /comment/assets/icon/arrow_down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /comment/assets/icon/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /comment/assets/icon/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /comment/assets/icon/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /comment/assets/icon/heart_on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /comment/assets/icon/reply.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /comment/assets/icon/tip.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /comment/components/Action.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /comment/components/Button.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /comment/components/Comment.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 166 | -------------------------------------------------------------------------------- /comment/components/Svg.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /comment/const.js: -------------------------------------------------------------------------------- 1 | export const GT_ACCESS_TOKEN = 'GT_ACCESS_TOKEN' 2 | export const GT_COMMENT = 'GT_COMMENT' 3 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/index.styl: -------------------------------------------------------------------------------- 1 | /* variables */ 2 | $gt-color-main := #6190e8 3 | $gt-color-sub := #a1a1a1 4 | $gt-color-loader := #999999 5 | $gt-color-error := #ff3860 6 | $gt-color-hr := #E9E9E9 7 | $gt-color-input-border := rgba(0,0,0,0.1) 8 | $gt-color-input-bg := #f6f6f6 9 | $gt-color-comment-bg := #f9f9f9 10 | $gt-color-comment-adminbg := #f6f9fe 11 | $gt-color-comment-txt := #333333 12 | $gt-color-link-active := #333333 13 | $gt-color-btn := #ffffff 14 | $gt-color-popbg := #ffffff 15 | $gt-size-base := 16px // default font-size 16 | $gt-size-border-radius := 5px 17 | $gt-breakpoint-mobile := 479px 18 | $gt-mask-z-index := 9999 19 | 20 | /* functions & mixins */ 21 | clearfix() { 22 | &:before, 23 | &:after { 24 | content: " "; 25 | display: table; 26 | } 27 | &:after { clear: both; } 28 | } 29 | em($px, $base-size = $gt-size-base) 30 | u = unit($px) 31 | if (u is 'px') 32 | unit($px / $base-size, 'em') 33 | else 34 | unit($px, u) 35 | 36 | mobile() 37 | @media (max-width: $gt-breakpoint-mobile) 38 | {block} 39 | 40 | /* variables - calculated */ 41 | $gt-size-loader-dot := em(6px) 42 | $gt-size-loader := em(28px) 43 | $gt-size-avatar := em(50px) 44 | $gt-size-avatar-mobi := em(32px) 45 | 46 | /* styles */ 47 | // Put everything under container to avoid style conflicts 48 | .gt-container 49 | box-sizing: border-box 50 | * 51 | box-sizing: border-box 52 | font-size: $gt-size-base 53 | // common 54 | a 55 | color: $gt-color-main 56 | &:hover 57 | color: lighten($gt-color-main, 20%) 58 | border-color: lighten($gt-color-main, 20%) 59 | &.is--active 60 | color: $gt-color-link-active 61 | cursor: default !important 62 | &:hover 63 | color: $gt-color-link-active 64 | .hide 65 | display: none !important 66 | // icons 67 | .gt-svg 68 | display: inline-block 69 | width: em(16px) 70 | height: em(16px) 71 | vertical-align: sub 72 | svg 73 | width: 100% 74 | height: 100% 75 | fill: $gt-color-main 76 | .gt-ico 77 | display: inline-block 78 | &-text 79 | margin-left: em(5px) 80 | &-github 81 | .gt-svg 82 | width: 100% 83 | height: 100% 84 | svg 85 | fill: inherit 86 | /* loader */ 87 | .gt-spinner 88 | position: relative 89 | &::before 90 | content: '' 91 | box-sizing: border-box 92 | position: absolute 93 | top: 3px 94 | width: em(12px) 95 | height: em(12px) 96 | margin-top: em(-3px) 97 | margin-left: em(-6px) 98 | border-radius: 50% 99 | border: 1px solid $gt-color-btn 100 | border-top-color: $gt-color-main 101 | animation: gt-kf-rotate .6s linear infinite 102 | 103 | .gt-loader 104 | position: relative 105 | border: 1px solid $gt-color-loader 106 | animation: ease gt-kf-rotate 1.5s infinite 107 | display: inline-block 108 | font-style: normal 109 | width: $gt-size-loader 110 | height: $gt-size-loader 111 | //font-size: $gt-size-loader 112 | line-height: $gt-size-loader 113 | border-radius: 50% 114 | &:before 115 | content: '' 116 | position: absolute 117 | display: block 118 | top: 0 119 | left: 50% 120 | margin-top: -($gt-size-loader-dot / 2) 121 | margin-left: -($gt-size-loader-dot / 2) 122 | width: $gt-size-loader-dot 123 | height: $gt-size-loader-dot 124 | background-color: $gt-color-loader 125 | border-radius: 50% 126 | // avatar 127 | .gt-avatar 128 | display: inline-block 129 | width: $gt-size-avatar 130 | height: $gt-size-avatar 131 | +mobile() 132 | width: $gt-size-avatar-mobi 133 | height: $gt-size-avatar-mobi 134 | img 135 | width: 100% 136 | height: auto 137 | border-radius: 3px 138 | &-github 139 | width: $gt-size-avatar - em(2px) 140 | height: $gt-size-avatar - em(2px) 141 | +mobile() 142 | width: $gt-size-avatar-mobi - em(2px) 143 | height: $gt-size-avatar-mobi - em(2px) 144 | // button 145 | .gt-btn 146 | padding: em(12px) em(20px) 147 | display: inline-block 148 | line-height: 1 149 | text-decoration: none 150 | white-space: nowrap 151 | cursor: pointer 152 | border: 1px solid $gt-color-main 153 | border-radius: $gt-size-border-radius 154 | background-color: $gt-color-main 155 | color: $gt-color-btn 156 | outline: none 157 | font-size: em(12px) 158 | &-text 159 | font-weight: 400 160 | &-loading 161 | position: relative 162 | margin-left: em(8px) 163 | display: inline-block 164 | width: em(12px) 165 | height: em(16px) 166 | vertical-align: top 167 | &.is--disable 168 | cursor: not-allowed 169 | opacity: 0.5 170 | &-login 171 | margin-right: 0 172 | &-preview 173 | background-color: $gt-color-btn 174 | color: $gt-color-main 175 | &:hover 176 | background-color: darken($gt-color-btn, 5%) 177 | border-color: lighten($gt-color-main, 20%) 178 | &-public 179 | &:hover 180 | background-color: lighten($gt-color-main, 20%) 181 | border-color: lighten($gt-color-main, 20%) 182 | &-loadmore 183 | // loadmore 184 | 185 | /* error */ 186 | .gt-error 187 | text-align: center 188 | margin: em(10px) 189 | color: $gt-color-error 190 | 191 | /* initing */ 192 | .gt-initing 193 | padding: em(20px) 0 194 | text-align: center 195 | &-text 196 | margin: em(10px) auto 197 | font-size: 92% 198 | 199 | /* no int */ 200 | .gt-no-init 201 | padding: em(20px) 0 202 | text-align: center 203 | 204 | /* link */ 205 | .gt-link 206 | border-bottom: 1px dotted $gt-color-main 207 | &-counts, &-project 208 | text-decoration: none 209 | 210 | /* meta */ 211 | .gt-meta 212 | margin: em(20px) 0 213 | padding: em(16px) 0 214 | position: relative 215 | border-bottom: 1px solid $gt-color-hr 216 | font-size: em(16px) 217 | position: relative 218 | z-index: 10 219 | clearfix() 220 | 221 | .gt-counts 222 | margin: 0 em(10px) 0 0 223 | 224 | .gt-user 225 | float: right 226 | margin: 0 227 | font-size: 92% 228 | &-pic 229 | width: 16px 230 | height: 16px 231 | vertical-align: top 232 | margin-right: em(8px) 233 | &-inner 234 | display: inline-block 235 | cursor: pointer 236 | .gt-ico 237 | margin: 0 0 0 em(5px) 238 | svg 239 | fill: inherit 240 | .is--poping 241 | .gt-ico 242 | svg 243 | fill: $gt-color-main 244 | 245 | .gt-version 246 | color: $gt-color-sub 247 | margin-left: em(6px) 248 | 249 | .gt-copyright 250 | margin: 0 em(15px) em(8px) 251 | border-top: 1px solid $gt-color-hr 252 | padding-top: em(8px) 253 | 254 | /* popup */ 255 | .gt-popup 256 | position: absolute 257 | right: 0 258 | top: em(38px) 259 | background: $gt-color-popbg 260 | display: inline-block 261 | border: 1px solid $gt-color-hr 262 | padding: em(10px) 0 263 | font-size: em(14px) 264 | letter-spacing: .5px 265 | .gt-action 266 | cursor: pointer 267 | display: block 268 | margin: em(8px) 0 269 | padding: 0 em(18px) 270 | position: relative 271 | text-decoration: none 272 | &.is--active 273 | &:before 274 | content: '' 275 | width: em(4px) 276 | height: em(4px) 277 | background: $gt-color-main 278 | position: absolute 279 | left: em(8px) 280 | top: em(7px) 281 | /* header */ 282 | .gt-header 283 | position: relative 284 | display: flex 285 | &-comment 286 | flex: 1 287 | margin-left: em(20px) 288 | +mobile() 289 | margin-left: em(14px) 290 | &-textarea 291 | padding: em(12px) 292 | display: block 293 | box-sizing: border-box 294 | width: 100% 295 | min-height: em(82px) 296 | max-height: em(240px) 297 | border-radius: $gt-size-border-radius 298 | border: 1px solid $gt-color-input-border 299 | font-size: em(14px) 300 | word-wrap: break-word 301 | resize: vertical 302 | background-color: $gt-color-input-bg 303 | outline: none 304 | transition: all 0.25s ease 305 | &:hover 306 | background-color: lighten($gt-color-input-bg, 50%) 307 | // box-shadow: 0 em(10px) em(60px) 0 $gt-color-input-bg 308 | &-preview 309 | padding: em(12px) 310 | border-radius: $gt-size-border-radius 311 | border: 1px solid $gt-color-input-border 312 | background-color: $gt-color-input-bg 313 | &-controls 314 | position: relative 315 | margin: em(12px) 0 0 316 | clearfix() 317 | +mobile() 318 | margin: 0 319 | &-tip 320 | font-size: em(14px) 321 | color: $gt-color-main 322 | text-decoration: none 323 | vertical-align: sub 324 | +mobile() 325 | display: none 326 | .gt-btn 327 | float: right 328 | margin-left: em(20px) 329 | +mobile() 330 | float: none 331 | width: 100% 332 | margin: em(12px) 0 0 333 | 334 | &:after 335 | content: '' 336 | position: fixed 337 | bottom: 100% 338 | left: 0 339 | right: 0 340 | top: 0 341 | opacity: 0 342 | &.gt-input-focused 343 | position: relative 344 | &:after 345 | content: '' 346 | position: fixed 347 | bottom: 0% 348 | left: 0 349 | right: 0 350 | top: 0 351 | background: #000 352 | opacity: 0.6 353 | transition: opacity .3s, bottom 0s 354 | z-index: $gt-mask-z-index 355 | .gt-header-comment 356 | z-index: $gt-mask-z-index + 1 357 | 358 | /* comments */ 359 | .gt-comments 360 | padding-top: em(20px) 361 | &-null 362 | text-align: center 363 | &-controls 364 | margin: em(20px) 0 365 | text-align: center 366 | 367 | /* comment */ 368 | .gt-comment 369 | position: relative 370 | padding: em(10px) 0 371 | display: flex 372 | &-content 373 | flex: 1 374 | margin-left: em(20px) 375 | padding: em(12px) em(16px) 376 | background-color: $gt-color-comment-bg 377 | overflow: auto 378 | transition: all ease 0.25s 379 | &:hover 380 | box-shadow: 0 em(10px) em(60px) 0 darken($gt-color-comment-bg, 2%) 381 | +mobile() 382 | margin-left: em(14px) 383 | padding: em(10px) em(12px) 384 | &-header 385 | margin-bottom: em(8px) 386 | font-size: em(14px) 387 | position: relative 388 | &-username 389 | font-weight: 500 390 | color: $gt-color-main 391 | text-decoration: none 392 | &:hover 393 | text-decoration: underline 394 | &-text 395 | margin-left: em(8px) 396 | color: $gt-color-sub 397 | &-date 398 | margin-left: em(8px) 399 | color: $gt-color-sub 400 | &-like, &-edit, &-reply 401 | position: absolute 402 | height: em(22px) 403 | &:hover 404 | cursor: pointer 405 | &-like 406 | top: 0 407 | right: em(32px) 408 | &-edit, &-reply 409 | top: 0 410 | right: 0 411 | &-body 412 | color: $gt-color-comment-txt !important 413 | &-admin 414 | .gt-comment-content 415 | background-color: $gt-color-comment-adminbg 416 | 417 | @keyframes gt-kf-rotate 418 | 0% 419 | transform: rotate(0) 420 | 100% 421 | transform: rotate(360deg) 422 | -------------------------------------------------------------------------------- /comment/index.vue: -------------------------------------------------------------------------------- 1 |