diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..f674849 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9daa824 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..10dd76d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 StickmY + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Layout.vue b/Layout.vue new file mode 100644 index 0000000..2fb211d --- /dev/null +++ b/Layout.vue @@ -0,0 +1,208 @@ + + + + + + \ No newline at end of file diff --git a/NotFound.vue b/NotFound.vue new file mode 100644 index 0000000..1dff8c5 --- /dev/null +++ b/NotFound.vue @@ -0,0 +1,26 @@ + + + diff --git a/Page.vue b/Page.vue new file mode 100644 index 0000000..11cd193 --- /dev/null +++ b/Page.vue @@ -0,0 +1,193 @@ + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..d097f0b --- /dev/null +++ b/README.md @@ -0,0 +1,211 @@ +# vuepress-theme-mufeng + +## Installation + +```bash +yarn add vuepress-theme-mufeng -S +``` +or with npm +```bash +npm install vuepress-theme-mufeng --save-dev +``` + +## Article + +**Render an overview of the article** +To generate a preview of the post on the cards, use excerpt by adding `` after the first paragraph or first few introductory lines in your post. + +``` +## What is Vue.js - +In this post I will talk about Vue.js + +Vue.js is awesome +``` + +As in the above form, adding the `` tag to the `md` file, will render the content before this tag into the articles list as their preview. + + +## Articles meta-data +Use [mufengblog shell](https://github.com/zhangximufeng/mufengblog-shell) to generate a new post with automatic date-time stamp, title and metadata etc. this helps the cards to sort according to date automatically, also filter the posts by tags etc. + +install shell with +```bash +yarn global add mufengblog-shell +``` +and then from your project's root dir, run +```bash +mufengblog post -p --page README.md +``` +like if your post is named javascript, just run +```bash +mufengblog post -p javascript --page README.md +``` + +this will create a folder called javascript and a `README.md` file in it with required data automatically. You can then make changes to this file like changing the title and metadata, tags etc. + +```yaml +title: Article title +# date is used for article sorting +date: 2017-08-15 10:27:26 +tag: # Article tag, can be a String or an Array + - js + - react +# Meta tags that can be used to crawl by search engines +meta: + - name: description + content: Some description about your post + - name: keywords # keywords Tags, will be queried when searching within pages + content: theme vuepress +``` +To let the theme filter by tags, add the following information alongside your previous themeConfig in `config.js` inside `.vuepress` folder + +## tags + +```js +module.exports = { + themeConfig: { + tags: true, + nav: [ + { text: 'TAGS', link: '/tags/', tags: true } + ] + } +} +``` + +the above configuration let's theme know that `TAGS` field in the navbar is specifically for browsing tags from posts. When you visit the above path, it looks like following: + +![](https://blog-1252181333.cossh.myqcloud.com/blog/180137.png) + +![](https://blog-1252181333.cossh.myqcloud.com/blog/180218.png) + +## Comment System + +Use `gitalk` for comment system, click [gitalk](https://github.com/gitalk/gitalk) for more details. + +But, don't support flipMoveOptions and render instane method + +## Configuration + +For your reference, I have put the configuration of my blog (`.vuepress/config.js`) here: + +```js +module.exports = { + // Enable custom themes + theme: 'mufeng', + title: 'mufeng', + description: 'vuepress theme mufeng', + head: [ + ['link', { rel: 'icon', href: `/favicon.ico` }] + ], + port: 3000, + // Google Analytics ID + ga: 'xxxxx', + // PWA support + serviceWorker: true, + // fuck IE + evergreen: true, + markdown: { + // markdown-it-anchor options + anchor: { permalink: true }, + // markdown-it-toc options + toc: { includeLevel: [1, 2] }, + config: md => { + md.use(require('markdown-it-task-lists')) // a checkbox TODO List plugin + .use(require('markdown-it-imsize'), { autofill: true }) // Support for custom md image size ![test](image.png =100x200) + } + }, + // Yubisaki theme specific configuration + themeConfig: { + // Blog background image + background: '/background/path', + tags: true, + // github card + github: 'github username', + // favicon image (logo) + logo: '/logo/path', + // Custom article title color + accentColor: '#ac3e40', + // Number of articles displayed per page + per_page: 5, + // The time format for creating an article. If not set, it will not be displayed. Optional [yyyy-MM-dd HH:mm:ss] + date_format: 'yyyy-MM-dd', + // options for comment (gitalk), don't support flipMoveOptions and render instane method + comment: { + clientID: 'GitHub Application Client ID', + clientSecret: 'GitHub Application Client Secret', + repo: 'GitHub repo', + owner: 'GitHub repo owner', + admin: ['GitHub repo owner and collaborators, only these guys can initialize github issues'], + perPage: 5, + distractionFreeMode: false // Facebook-like distraction free mode + }, + // customize the links on the navigation bar + nav: [ + { text: 'HOME', link: '/', root: true }, // Specify this as the root directory of the blog post + { text: 'TAGS', link: '/tags/', tags: true }, // Specify the tags directory + { text: 'GITHUB', link: 'https://github.com/zhangximufeng' }, + { text: 'about me', link: '/about/' }, + ] + } +} +``` + + +## customize the layout + +Besides the basic `yaml` config generated by `mufengblog-shell`, you can add the following information to customize the layout as you want: + +to customize the layout, add the following to the header of the `markdown` file + +```yaml +heroText: Mufeng # title +activity: true # Use a custom activity layout that will collapse the card bar on the right +hidden: true # Set whether to display in the article list +tagline: Vuepress blog theme # description +heroImage: /static/logo.png # logo +# Refer to the configuration of the official default theme for service static files +actionText: Learn about → +actionLink: /mufeng/usage.html +# If you want to have more than one action button (in this case actionText and actionLink will be ignored): +# actions : +# - text : Action1 +# link : /mufeng/action1.html +# - text : Action2 +# link : /mufeng/action2.html +features: + - title: what is this + details: A vuepress-based blog theme based on the default theme provided by vuepress + - title: What are the characteristics? + details: Provide article list, article pagination, article details, github card, custom event page layout, etc. + - title: TODO + details: Tag cloud, TAG ARCHIVE, some scripts, some out of the box layout +footer: by stickmy +``` + +## Development, deployment + +**In the docs directory (or the root of your project), be sure to put a markdown file called README.md for generating the root path, which can be an empty file** + +You can use the following scripts to run the vuepress commands or you can run them directly, whichever you prefer + +`package.json`: + +```js +{ + "scripts": { + "docs:dev": "vuepress dev {dirName}", + "docs:build": "vuepress build {dirName}" + } +} +``` +If you haven't installed vuepress gloablly, these scripts will be helpful to find the vuepress binaries from `node_modules/.bin` directory and execute them on shell. to execute above scripts, run: +```bash +npm run docs:dev +``` + +or +```bash +npm run docs:build +``` +Accordingly. + diff --git a/animation/particleBoom.vue b/animation/particleBoom.vue new file mode 100644 index 0000000..c3ef9cb --- /dev/null +++ b/animation/particleBoom.vue @@ -0,0 +1,144 @@ + + + diff --git a/assets/search.svg b/assets/search.svg new file mode 100644 index 0000000..03d8391 --- /dev/null +++ b/assets/search.svg @@ -0,0 +1 @@ + diff --git a/components/AnimationButton.vue b/components/AnimationButton.vue new file mode 100644 index 0000000..8dcbc70 --- /dev/null +++ b/components/AnimationButton.vue @@ -0,0 +1,48 @@ + + + diff --git a/components/ArticleCard.vue b/components/ArticleCard.vue new file mode 100644 index 0000000..a4edd76 --- /dev/null +++ b/components/ArticleCard.vue @@ -0,0 +1,113 @@ + + + + + + + \ No newline at end of file diff --git a/components/ArticleGroup.vue b/components/ArticleGroup.vue new file mode 100644 index 0000000..7c2b84f --- /dev/null +++ b/components/ArticleGroup.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/components/DropdownLink.vue b/components/DropdownLink.vue new file mode 100644 index 0000000..c689360 --- /dev/null +++ b/components/DropdownLink.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/components/GithubCard.vue b/components/GithubCard.vue new file mode 100644 index 0000000..05c5ae1 --- /dev/null +++ b/components/GithubCard.vue @@ -0,0 +1,162 @@ + + + + + + + \ No newline at end of file diff --git a/components/LeetCodeCard.vue b/components/LeetCodeCard.vue new file mode 100644 index 0000000..aff2e7e --- /dev/null +++ b/components/LeetCodeCard.vue @@ -0,0 +1,64 @@ + + + + + + \ No newline at end of file diff --git a/components/LeetCodeGroup.vue b/components/LeetCodeGroup.vue new file mode 100644 index 0000000..47609a7 --- /dev/null +++ b/components/LeetCodeGroup.vue @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/components/NavLink.vue b/components/NavLink.vue new file mode 100644 index 0000000..e2e801b --- /dev/null +++ b/components/NavLink.vue @@ -0,0 +1,36 @@ + + + diff --git a/components/NavLinks.vue b/components/NavLinks.vue new file mode 100644 index 0000000..56c7f41 --- /dev/null +++ b/components/NavLinks.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/components/Navbar.vue b/components/Navbar.vue new file mode 100644 index 0000000..c5b5c66 --- /dev/null +++ b/components/Navbar.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/components/OutboundLink.vue b/components/OutboundLink.vue new file mode 100644 index 0000000..a3aa7f2 --- /dev/null +++ b/components/OutboundLink.vue @@ -0,0 +1,12 @@ + + + diff --git a/components/Pagation.vue b/components/Pagation.vue new file mode 100644 index 0000000..0653ad1 --- /dev/null +++ b/components/Pagation.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/components/SWUpdatePopup.vue b/components/SWUpdatePopup.vue new file mode 100644 index 0000000..35b52af --- /dev/null +++ b/components/SWUpdatePopup.vue @@ -0,0 +1,87 @@ + + + + + \ No newline at end of file diff --git a/components/SearchBox.vue b/components/SearchBox.vue new file mode 100644 index 0000000..4e3d252 --- /dev/null +++ b/components/SearchBox.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/components/Sidebar.vue b/components/Sidebar.vue new file mode 100644 index 0000000..0270678 --- /dev/null +++ b/components/Sidebar.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/components/SidebarButton.vue b/components/SidebarButton.vue new file mode 100644 index 0000000..0f1c21f --- /dev/null +++ b/components/SidebarButton.vue @@ -0,0 +1,28 @@ + + + diff --git a/components/SidebarGroup.vue b/components/SidebarGroup.vue new file mode 100644 index 0000000..2be890a --- /dev/null +++ b/components/SidebarGroup.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/components/SidebarLink.vue b/components/SidebarLink.vue new file mode 100644 index 0000000..c184cc1 --- /dev/null +++ b/components/SidebarLink.vue @@ -0,0 +1,86 @@ + + + diff --git a/components/Tag.vue b/components/Tag.vue new file mode 100644 index 0000000..5ea99f1 --- /dev/null +++ b/components/Tag.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/components/Tags.vue b/components/Tags.vue new file mode 100644 index 0000000..7a80a30 --- /dev/null +++ b/components/Tags.vue @@ -0,0 +1,69 @@ + + + + + + diff --git a/components/ToolGroup.vue b/components/ToolGroup.vue new file mode 100644 index 0000000..b2029ce --- /dev/null +++ b/components/ToolGroup.vue @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/enhanceApp.js b/enhanceApp.js new file mode 100644 index 0000000..f8f9657 --- /dev/null +++ b/enhanceApp.js @@ -0,0 +1,11 @@ +import routes from './enhancers/routes'; +import tags from './enhancers/tags'; +import optionHandler from './enhancers/optionHandler'; + +export default ({ Vue, options, router, siteData }) => { + const { themeConfig, pages } = siteData; + + Vue.use(optionHandler, { themeConfig }); + Vue.use(routes, { router, themeConfig }); + Vue.use(tags, { router, pages, themeConfig }); +} \ No newline at end of file diff --git a/enhancers/optionHandler.js b/enhancers/optionHandler.js new file mode 100644 index 0000000..b963c67 --- /dev/null +++ b/enhancers/optionHandler.js @@ -0,0 +1,46 @@ +/** + * Handle the options you need later, mount them on Vue + * @param {*} themeConfig theme configuration + */ +const install = (Vue, { themeConfig }) => { + const TAGS = '/tags/'; + const ROOT = '/'; + + const navs = themeConfig.nav; + + Vue.options = Vue.options || {}; + + const tagsOption = { + useTag: themeConfig.tags, + path: TAGS + } + + const rootOption = { + path: ROOT + } + + navs.forEach(nav => { + if (nav.tags && nav.link) { + tagsOption.path = nav.link; + } + if (nav.root && nav.link) { + rootOption.path = nav.link; + } + }) + + Vue.options.tags = tagsOption; + Vue.options.root = rootOption; + + Vue.mixin({ + computed: { + $tagOptions() { + return Object.assign({}, tagsOption); + }, + $rootOptions() { + return Object.assign({}, rootOption); + } + } + }) +} + +export default { install } diff --git a/enhancers/routes.js b/enhancers/routes.js new file mode 100644 index 0000000..e1b51b0 --- /dev/null +++ b/enhancers/routes.js @@ -0,0 +1,62 @@ +const Layout = () => import('../Layout'); + +/** + * Injection route + * @param {*} Vue + * @param {*} param1 + */ +const install = (Vue, { router, themeConfig }) => { + const navs = navsLocale(themeConfig.nav); + const routes = []; + + // Get the directory with the index set in the directory structure via redirect + const navInRouter = router.options.routes + .filter(route => route.redirect) + .map(route => route.redirect); + + // Inject root + navs.forEach(nav => { + if (nav.root && + nav.link && + !~navInRouter.indexOf(nav.link)) { + routes.push({ + path: nav.link, + component: Layout, + name: `nav-${nav.text}`, + meta: { root: true } + }) + } + }); + + // Inject tags + if (Vue.options.tags.useTag) { + routes.push({ + path: `${Vue.options.tags.path}:tagName?`, + component: Layout, + meta: { tag: true } + }) + } + + router.addRoutes(routes); +} + +const hasPath = (router, path) => { + const routes = router.options.routes; + for(let route of routes) { + if (route.path === path) return true; + } + return false; +} + +/** + * Get local nav link + * Filter out http | https | // header + * @param {Array} navs + */ +const navsLocale = (navs) => { + const localeReg = /^\/(?!\/).*/; + + return navs.filter(nav => nav.link && localeReg.test(nav.link)) +} + +export default { install } \ No newline at end of file diff --git a/enhancers/tags.js b/enhancers/tags.js new file mode 100644 index 0000000..9283a6c --- /dev/null +++ b/enhancers/tags.js @@ -0,0 +1,44 @@ +import { pageNormalize } from '../lib/util'; + +/** + * Extract the tags of the article + * @param {*} Vue + * @param {*} param1 + */ +const install = (Vue, { router, pages, themeConfig }) => { + const navs = themeConfig.nav; + const pagesWithoutLayout = pageNormalize(pages, navs); + + const tagMap = {}; + + pagesWithoutLayout.forEach(page => { + if (page.frontmatter && page.frontmatter.tag) { + const tag = page.frontmatter.tag; + if (typeof tag === 'string') { + page.tags = [tag]; + insertTag(tagMap, tag, page.key); + } else { + page.tags = tag; + tag.forEach(t => insertTag(tagMap, t, page.key)); + } + } + }) + + Vue.mixin({ + computed: { + $tags() { + return tagMap; + } + } + }); +} + +const insertTag = (tagMap, tag, key) => { + if (!tagMap[tag]) { + tagMap[tag] = [key]; + } else { + tagMap[tag].push(key); + } +} + +export default { install }; diff --git a/layout/Activity.vue b/layout/Activity.vue new file mode 100644 index 0000000..4b424cf --- /dev/null +++ b/layout/Activity.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/lib/animation.js b/lib/animation.js new file mode 100644 index 0000000..e0b13f0 --- /dev/null +++ b/lib/animation.js @@ -0,0 +1,45 @@ +/** + * 粒子原型, 提供一个绘图函数 + */ +export function ExplodingParticle() { + this.animationDuration = 1000; // in ms + + // Set the speed for our particle + this.speed = { + x: -5 + Math.random() * 10, + y: -5 + Math.random() * 10 + }; + + // Size our particle + this.radius = 5 + Math.random() * 5; + + // Set a max time to live for our particle + this.life = 30 + Math.random() * 10; + this.remainingLife = this.life; + + // This function will be called by our animation logic later on + this.draw = ctx => { + let p = this; + + if (this.remainingLife > 0 && this.radius > 0) { + // Draw a circle at the current location + ctx.beginPath(); + ctx.arc(p.startX, p.startY, p.radius, 0, Math.PI * 2); + ctx.fillStyle = + "rgba(" + + this.rgbArray[0] + + "," + + this.rgbArray[1] + + "," + + this.rgbArray[2] + + ", 1)"; + ctx.fill(); + + // Update the particle's location and life + p.remainingLife--; + p.radius -= 0.25; + p.startX += p.speed.x; + p.startY += p.speed.y; + } + }; +}; diff --git a/lib/comment.mixin.js b/lib/comment.mixin.js new file mode 100644 index 0000000..ee8f121 --- /dev/null +++ b/lib/comment.mixin.js @@ -0,0 +1,12 @@ +import Gitalk from 'gitalk' + +const commentMixin = { + methods: { + comment() { + if (!this.$site.themeConfig.comment) return; + return new Gitalk(this.$site.themeConfig.comment); + } + } +} + +export default commentMixin; diff --git a/lib/navLayout.mixin.js b/lib/navLayout.mixin.js new file mode 100644 index 0000000..bf1ff68 --- /dev/null +++ b/lib/navLayout.mixin.js @@ -0,0 +1,33 @@ +import { pageWithCustomLayout, pageNormalize, navLayoutRE } from './util' + +export default { + computed: { + // Handle whether the current route is nav layout, if yes, only show layoutTag for this layout page + isNavLayout() { + const navsWithLayout = this.$site.themeConfig.nav.filter(nav => nav.layout) + return navsWithLayout.some(nav => nav.link === this.$route.path) + }, + // Handling leetcode layout + isLeetCode() { + const layout = currentLayout(this.$site.themeConfig.nav, this.$route.path) + return this.isNavLayout && layout === 'leetcode' + }, + pages() { + const { nav } = this.$site.themeConfig + if (this.isNavLayout) { + // get layout from current $route.path + const layout = currentLayout(nav, this.$route.path) + return pageWithCustomLayout(this.$site.pages, nav, layout) + } + return pageNormalize(this.$site.pages, nav) + }, + perPage() { + return (this.$site.themeConfig['per_page'] || 5) + } + } +} + +function currentLayout(navs, routePath) { + const routes = navs.filter(nav => nav.link === routePath) + return routes.length > 0 ? routes[0].layout : null +} diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 0000000..6513ca0 --- /dev/null +++ b/lib/util.js @@ -0,0 +1,319 @@ +export const hashRE = /#.*$/ +export const indexRE = /(.+\/)index$/ +export const navLayoutRE = /(\/.+\/)(.*)$/g +export const extRE = /\.(md|html)$/ +export const endingSlashRE = /\/$/ +export const outboundRE = /^(https?:|mailto:)/ + +export function normalize (path) { + return path + .replace(hashRE, '') + .replace(extRE, '') +} + +export function getHash (path) { + const match = path.match(hashRE) + if (match) { + return match[0] + } +} + +export function isExternal (path) { + return outboundRE.test(path) +} + +export function isMailto (path) { + return /^mailto:/.test(path) +} + +export function ensureExt (path) { + if (isExternal(path)) { + return path + } + const hashMatch = path.match(hashRE) + const hash = hashMatch ? hashMatch[0] : '' + const normalized = normalize(path) + + if (endingSlashRE.test(normalized)) { + return path + } + return normalized + '.html' + hash +} + +export function isActive (route, path) { + const routeHash = route.hash + const linkHash = getHash(path) + if (linkHash && routeHash !== linkHash) { + return false + } + const routePath = normalize(route.path) + const pagePath = normalize(path) + if (endingSlashRE.test(routePath) || endingSlashRE.test(pagePath)) { + return routePath === pagePath + } else { + return routePath.indexOf(pagePath) === 0 + } +} + +export function getTitle (siteTitle, page) { + if(page.frontmatter.activity) { + return page.frontmatter.title || siteTitle + } + + return siteTitle +} + +export function layoutsFromNav(navs) { + return navs.reduce((layouts, nav) => + layouts.concat(nav.layout || []), []) +} + +export function excludeFeature (page) { + return (!page.frontmatter.date || + +new Date(page.frontmatter.date) <= +new Date()) +} + +// Filter out pages with a unique layoutTag flag +export function pageWithCustomLayout(pages, navs, layout) { + const navList = navs || [] + const navLinks = navList.map(n => navsLinksNormalize(n.link)) + const pagesWithoutRoot = pages + .filter( + page => page.path !== '/' && // exclude root + excludeFeature(page) && // exclude page.date > current time + !~navLinks.indexOf(page.path) && // nav link + !isHidden(page) && // page.frontmatter.hidden + page.frontmatter.layoutTag === layout // layout tag + ) + return pageSortByDate(pagesWithoutRoot) +} + +// Pages that appear in themeConfig.nav, as well as pages with layoutTag in nav are filtered +export function pageNormalize(pages, navs) { + const layouts = layoutsFromNav(navs) + const navList = navs || [] + const navLinks = navList.map(n => navsLinksNormalize(n.link)) + const withoutRoot = pages + .filter( + page => page.path !== '/' && + excludeFeature(page) && // exclude page.date > current time + !~layouts.indexOf(page.frontmatter.layoutTag) && + !~navLinks.indexOf(page.path) && + !isHidden(page) + ) + return pageSortByDate(withoutRoot) +} + +// /about/index ==> /about/ +export function navsLinksNormalize(link) { + const re = link.match(indexRE) + return re ? re[1] : link +} + +export function isHidden(page) { + if(!page.frontmatter) return false + return page.frontmatter.hidden +} + +// pages sort by date +export function pageSortByDate (pages) { + const pageWithDate = pages + .filter(page => page.frontmatter.date ) + const pageWithOutDate = pages + .filter(page => !page.frontmatter.date ) + + let mapped = pageWithDate + .map((p, i) => ({ index: i, date: +new Date(p.frontmatter.date) })) + + mapped.sort((a, b) => { + if(a.date > b.date) { + return -1 + } + if(a.date < b.date) { + return 1 + } + return 0 + }) + return mapped.map(m => pageWithDate[m.index]).concat(pageWithOutDate) +} + +export function resolvePage (pages, rawPath, base) { + if (base) { + rawPath = resolvePath(rawPath, base) + } + const path = normalize(rawPath) + for (let i = 0; i < pages.length; i++) { + if (normalize(pages[i].path) === path) { + return Object.assign({}, pages[i], { + type: 'page', + path: ensureExt(rawPath) + }) + } + } + console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`) + return {} +} + +function resolvePath (relative, base, append) { + const firstChar = relative.charAt(0) + if (firstChar === '/') { + return relative + } + + if (firstChar === '?' || firstChar === '#') { + return base + relative + } + + const stack = base.split('/') + + // remove trailing segment if: + // - not appending + // - appending to trailing slash (last segment is empty) + if (!append || !stack[stack.length - 1]) { + stack.pop() + } + + // resolve relative path + const segments = relative.replace(/^\//, '').split('/') + for (let i = 0; i < segments.length; i++) { + const segment = segments[i] + if (segment === '..') { + stack.pop() + } else if (segment !== '.') { + stack.push(segment) + } + } + + // ensure leading slash + if (stack[0] !== '') { + stack.unshift('') + } + + return stack.join('/') +} + +export function resolveSidebarItems (page, route, site, localePath) { + const pageSidebarConfig = page.frontmatter.sidebar + if (pageSidebarConfig === 'auto') { + return resolveHeaders(page) + } + const { pages, themeConfig } = site + + const localeConfig = localePath && themeConfig.locales + ? themeConfig.locales[localePath] || themeConfig + : themeConfig + + const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar + if (!sidebarConfig) { + return [] + } else { + const { base, config } = resolveMatchingConfig(route, sidebarConfig) + return config + ? config.map(item => resolveItem(item, pages, base)) + : [] + } +} + +function resolveHeaders (page) { + const headers = groupHeaders(page.headers || []) + return [{ + type: 'group', + collapsable: false, + title: page.title, + children: headers.map(h => ({ + type: 'auto', + title: h.title, + basePath: page.path, + path: page.path + '#' + h.slug, + children: h.children || [] + })) + }] +} + +export function groupHeaders (headers) { + // group h3s under h2 + headers = headers.map(h => Object.assign({}, h)) + let lastH2 + headers.forEach(h => { + if (h.level === 2) { + lastH2 = h + } else if (lastH2) { + (lastH2.children || (lastH2.children = [])).push(h) + } + }) + return headers.filter(h => h.level === 2) +} + +export function resolveNavLinkItem (linkItem) { + return Object.assign(linkItem, { + type: linkItem.items && linkItem.items.length ? 'links' : 'link' + }) +} + +export function resolveMatchingConfig (route, config) { + if (Array.isArray(config)) { + return { + base: '/', + config: config + } + } + for (const base in config) { + if (ensureEndingSlash(route.path).indexOf(base) === 0) { + return { + base, + config: config[base] + } + } + } + return {} +} + +function ensureEndingSlash (path) { + return /(\.html|\/)$/.test(path) + ? path + : path + '/' +} + +function resolveItem (item, pages, base, isNested) { + if (typeof item === 'string') { + return resolvePage(pages, item, base) + } else if (Array.isArray(item)) { + return Object.assign(resolvePage(pages, item[0], base), { + title: item[1] + }) + } else { + if (isNested) { + console.error( + '[vuepress] Nested sidebar groups are not supported. ' + + 'Consider using navbar + categories instead.' + ) + } + const children = item.children || [] + return { + type: 'group', + title: item.title, + children: children.map(child => resolveItem(child, pages, base, true)), + collapsable: item.collapsable !== false + } + } +} + +Date.prototype.Format = function (fmt) { + var o = { + "y+": this.getFullYear(), + "M+": this.getMonth() + 1, // month + "d+": this.getDate(), // day + "H+": this.getHours(), // hour + "m+": this.getMinutes(), // Minute + "s+": this.getSeconds(), // Seconds + "q+": Math.floor((this.getMonth() + 3) / 3), // Quarter + "S": this.getMilliseconds() // millisecond + }; + if (!fmt) fmt = 'yyyy-MM-dd HH:mm:ss' + if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); + for (var k in o) + if (new RegExp("(" + k + ")").test(fmt)) { + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); + } + return fmt; +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..2da99f7 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "vuepress-theme-mufeng", + "version": "2.0.1", + "description": "vuepress-theme", + "repository": "https://github.com/zhangximufeng/vuepress-theme-mufeng", + "homepage": "https://github.com/zhangximufeng/vuepress-theme-mufeng/blob/master/README.md", + "main": "util.js", + "keywords": [ + "vuepress", + "theme", + "vuepress-theme" + ], + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "zhangximufeng", + "license": "ISC", + "dependencies": { + "autosize": "^4.0.2", + "axios": "^0.18.0", + "date-fns": "^1.29.0", + "github-markdown-css": "^2.10.0", + "html2canvas": "^1.0.0-alpha.12", + "node-polyglot": "^2.3.0", + "raw-loader": "^0.5.1" + } +} diff --git a/package/comment/assets/icon/arrow_down.svg b/package/comment/assets/icon/arrow_down.svg new file mode 100644 index 0000000..d20fe20 --- /dev/null +++ b/package/comment/assets/icon/arrow_down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package/comment/assets/icon/edit.svg b/package/comment/assets/icon/edit.svg new file mode 100644 index 0000000..ffb84e5 --- /dev/null +++ b/package/comment/assets/icon/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/package/comment/assets/icon/github.svg b/package/comment/assets/icon/github.svg new file mode 100644 index 0000000..36e758f --- /dev/null +++ b/package/comment/assets/icon/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/package/comment/assets/icon/heart.svg b/package/comment/assets/icon/heart.svg new file mode 100644 index 0000000..fcc6240 --- /dev/null +++ b/package/comment/assets/icon/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/package/comment/assets/icon/heart_on.svg b/package/comment/assets/icon/heart_on.svg new file mode 100644 index 0000000..3d5cc5b --- /dev/null +++ b/package/comment/assets/icon/heart_on.svg @@ -0,0 +1,3 @@ + + + diff --git a/package/comment/assets/icon/reply.svg b/package/comment/assets/icon/reply.svg new file mode 100644 index 0000000..11af6f1 --- /dev/null +++ b/package/comment/assets/icon/reply.svg @@ -0,0 +1,3 @@ + + + diff --git a/package/comment/assets/icon/tip.svg b/package/comment/assets/icon/tip.svg new file mode 100644 index 0000000..20ea0b2 --- /dev/null +++ b/package/comment/assets/icon/tip.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/package/comment/components/Action.vue b/package/comment/components/Action.vue new file mode 100644 index 0000000..8ce7778 --- /dev/null +++ b/package/comment/components/Action.vue @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/package/comment/components/Button.vue b/package/comment/components/Button.vue new file mode 100644 index 0000000..fd0154c --- /dev/null +++ b/package/comment/components/Button.vue @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/package/comment/components/Comment.vue b/package/comment/components/Comment.vue new file mode 100644 index 0000000..a31ee2a --- /dev/null +++ b/package/comment/components/Comment.vue @@ -0,0 +1,165 @@ + + + diff --git a/package/comment/components/Svg.vue b/package/comment/components/Svg.vue new file mode 100644 index 0000000..43bad8e --- /dev/null +++ b/package/comment/components/Svg.vue @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/package/comment/const.js b/package/comment/const.js new file mode 100644 index 0000000..ce6e0a2 --- /dev/null +++ b/package/comment/const.js @@ -0,0 +1,2 @@ +export const GT_ACCESS_TOKEN = 'GT_ACCESS_TOKEN' +export const GT_COMMENT = 'GT_COMMENT' diff --git a/package/comment/graphql/getComments.js b/package/comment/graphql/getComments.js new file mode 100644 index 0000000..c815d6d --- /dev/null +++ b/package/comment/graphql/getComments.js @@ -0,0 +1,133 @@ +import { axiosGithub } from "../util"; + +const getQL = (vars, pagerDirection) => { + const cursorDirection = pagerDirection === "last" ? "before" : "after"; + const ql = ` + query getIssueAndComments( + $owner: String!, + $repo: String!, + $id: Int!, + $cursor: String, + $pageSize: Int! + ) { + repository(owner: $owner, name: $repo) { + issue(number: $id) { + title + url + bodyHTML + createdAt + comments(${pagerDirection}: $pageSize, ${cursorDirection}: $cursor) { + totalCount + pageInfo { + ${pagerDirection === "last" ? "hasPreviousPage" : "hasNextPage"} + ${cursorDirection === "before" ? "startCursor" : "endCursor"} + } + nodes { + id + databaseId + author { + avatarUrl + login + url + } + bodyHTML + body + createdAt + reactions(first: 100, content: HEART) { + totalCount + viewerHasReacted + pageInfo{ + hasNextPage + } + nodes { + id + databaseId + user { + login + } + } + } + } + } + } + } + } + `; + + if (vars.cursor === null) delete vars.cursor; + + return { + operationName: "getIssueAndComments", + query: ql, + variables: vars + }; +}; + +function getComments(issue) { + const { + owner, + repo, + perPage, + pagerDirection, + defaultAuthor + } = this.options; + return axiosGithub + .post( + "/graphql", + getQL( + { + owner, + repo, + id: issue.number, + pageSize: perPage, + cursor: this.cursor + }, + pagerDirection + ), + { + headers: { + Authorization: `bearer ${this.accessToken}` + } + } + ) + .then(res => { + const data = res.data.data.repository.issue.comments; + const items = data.nodes.map(node => { + const author = node.author || defaultAuthor; + + return { + id: node.databaseId, + gId: node.id, + user: { + avatar_url: author.avatarUrl, + login: author.login, + html_url: author.url + }, + created_at: node.createdAt, + body_html: node.bodyHTML, + body: node.body, + html_url: `https://github.com/${owner}/${repo}/issues/${issue.number}#issuecomment-${node.databaseId}`, + reactions: node.reactions + }; + }); + + let cs; + + if (pagerDirection === "last") { + cs = [...items, ...this.comments]; + } else { + cs = [...this.comments, ...items]; + } + + const isLoadOver = + data.pageInfo.hasPreviousPage === false || + data.pageInfo.hasNextPage === false; + this.comments = cs; + this.isLoadOver = isLoadOver; + this.cursor = data.pageInfo.startCursor || data.pageInfo.endCursor; + + return cs; + }); +} + +export default getComments; diff --git a/package/comment/i18n/en.json b/package/comment/i18n/en.json new file mode 100644 index 0000000..7bac7a3 --- /dev/null +++ b/package/comment/i18n/en.json @@ -0,0 +1,20 @@ +{ + "init": "Gitalking ...", + "no-found-related": "Related %{link} not found", + "please-contact": "Please contact %{user} to initialize the comment", + "init-issue": "Init Issue", + "leave-a-comment": "Leave a comment", + "preview": "Preview", + "edit": "Edit", + "comment": "Comment", + "support-markdown": "Markdown is supported", + "login-with-github": "Login with GitHub", + "first-comment-person": "Be the first guy leaving a comment!", + "commented": "commented", + "load-more": "Load more", + "counts": "%{counts} comment |||| %{counts} comments", + "sort-asc": "Sort by Oldest", + "sort-desc": "Sort by Latest", + "logout": "Logout", + "anonymous": "Anonymous" +} diff --git a/package/comment/i18n/es-ES.json b/package/comment/i18n/es-ES.json new file mode 100644 index 0000000..4331daa --- /dev/null +++ b/package/comment/i18n/es-ES.json @@ -0,0 +1,20 @@ +{ + "init": "Gitalking ...", + "no-found-related": "Link %{link} no encontrado", + "please-contact": "Por favor contacta con %{user} para inicializar el comentario", + "init-issue": "Iniciar Issue", + "leave-a-comment": "Deja un comentario", + "preview": "Avance", + "edit": "Editar", + "comment": "Comentario", + "support-markdown": "Markdown es soportado", + "login-with-github": "Entrar con GitHub", + "first-comment-person": "Sé el primero en dejar un comentario!", + "commented": "comentó", + "load-more": "Cargar más", + "counts": "%{counts} comentario |||| %{counts} comentarios", + "sort-asc": "Ordenar por Antiguos", + "sort-desc": "Ordenar por Recientes", + "logout": "Salir", + "anonymous": "Anónimo" +} diff --git a/package/comment/i18n/fr.json b/package/comment/i18n/fr.json new file mode 100644 index 0000000..9242d36 --- /dev/null +++ b/package/comment/i18n/fr.json @@ -0,0 +1,20 @@ +{ + "init": "Gitalking ...", + "no-found-related": "Lien %{link} non trouvé", + "please-contact": "S’il vous plaît contactez %{user} pour initialiser les commentaires", + "init-issue": "Initialisation des issues", + "leave-a-comment": "Laisser un commentaire", + "preview": "Aperçu", + "edit": "Modifier", + "comment": "Commentaire", + "support-markdown": "Markdown est supporté", + "login-with-github": "Se connecter avec GitHub", + "first-comment-person": "Être le premier à laisser un commentaire !", + "commented": "commenter", + "load-more": "Charger plus", + "counts": "%{counts} commentaire |||| %{counts} commentaires", + "sort-asc": "Trier par plus ancien", + "sort-desc": "Trier par plus récent", + "logout": "Déconnexion", + "anonymous": "Anonyme" +} diff --git a/package/comment/i18n/index.js b/package/comment/i18n/index.js new file mode 100644 index 0000000..ff22310 --- /dev/null +++ b/package/comment/i18n/index.js @@ -0,0 +1,24 @@ +import Polyglot from 'node-polyglot' +import ZHCN from './zh-CN.json' +import ZHTW from './zh-TW.json' +import EN from './en.json' +import ES from './es-ES.json' +import FR from './fr.json' +import RU from './ru.json' + +const i18nMap = { + 'zh': ZHCN, + 'zh-CN': ZHCN, + 'zh-TW': ZHTW, + 'en': EN, + 'es-ES': ES, + 'fr': FR, + 'ru': RU, +} + +export default function (language) { + return new Polyglot({ + phrases: i18nMap[language] || i18nMap.en, + locale: language + }) +} diff --git a/package/comment/i18n/ru.json b/package/comment/i18n/ru.json new file mode 100644 index 0000000..4c1d868 --- /dev/null +++ b/package/comment/i18n/ru.json @@ -0,0 +1,20 @@ +{ + "init": "Gitalking ...", + "no-found-related": "Связанные %{link} не найдены", + "please-contact": "Пожалуйста, свяжитесь с %{user} чтобы инициализировать комментарий", + "init-issue": "Выпуск инициализации", + "leave-a-comment": "Оставить комментарий", + "preview": "Предварительный просмотр", + "edit": "Pедактировать", + "comment": "Комментарий", + "support-markdown": "Поддерживается Markdown", + "login-with-github": "Вход через GitHub", + "first-comment-person": "Будьте первым, кто оставил комментарий", + "commented": "прокомментированный", + "load-more": "Загрузить ещё", + "counts": "%{counts} комментарий |||| %{counts} комментарьев", + "sort-asc": "Сортировать по старым", + "sort-desc": "Сортировать по последним", + "logout": "Выход", + "anonymous": "Анонимный" +} diff --git a/package/comment/i18n/zh-CN.json b/package/comment/i18n/zh-CN.json new file mode 100644 index 0000000..4715928 --- /dev/null +++ b/package/comment/i18n/zh-CN.json @@ -0,0 +1,20 @@ +{ + "init": "Gitalk 加载中 ...", + "no-found-related": "未找到相关的 %{link} 进行评论", + "please-contact": "请联系 %{user} 初始化创建", + "init-issue": "初始化 Issue", + "leave-a-comment": "说点什么", + "preview": "预览", + "edit": "编辑", + "comment": "评论", + "support-markdown": "支持 Markdown 语法", + "login-with-github": "使用 GitHub 登录", + "first-comment-person": "来做第一个留言的人吧!", + "commented": "发表于", + "load-more": "加载更多", + "counts": "%{counts} 条评论", + "sort-asc": "从旧到新排序", + "sort-desc": "从新到旧排序", + "logout": "注销", + "anonymous": "未登录用户" +} diff --git a/package/comment/i18n/zh-TW.json b/package/comment/i18n/zh-TW.json new file mode 100644 index 0000000..91229f8 --- /dev/null +++ b/package/comment/i18n/zh-TW.json @@ -0,0 +1,20 @@ +{ + "init": "Gitalk 載入中…", + "no-found-related": "未找到相關的 %{link}", + "please-contact": "請聯絡 %{user} 初始化評論", + "init-issue": "初始化 Issue", + "leave-a-comment": "寫點什麼", + "preview": "預覽", + "edit": "編輯", + "comment": "評論", + "support-markdown": "支援 Markdown 語法", + "login-with-github": "使用 GitHub 登入", + "first-comment-person": "成為首個留言的人吧!", + "commented": "評論於", + "load-more": "載入更多", + "counts": "%{counts} 筆評論", + "sort-asc": "從舊至新排序", + "sort-desc": "從新至舊排序", + "logout": "登出", + "anonymous": "訪客" +} diff --git a/package/comment/index.styl b/package/comment/index.styl new file mode 100644 index 0000000..03134db --- /dev/null +++ b/package/comment/index.styl @@ -0,0 +1,422 @@ +/* variables */ +$gt-color-main := #6190e8 +$gt-color-sub := #a1a1a1 +$gt-color-loader := #999999 +$gt-color-error := #ff3860 +$gt-color-hr := #E9E9E9 +$gt-color-input-border := rgba(0,0,0,0.1) +$gt-color-input-bg := #f6f6f6 +$gt-color-comment-bg := #f9f9f9 +$gt-color-comment-adminbg := #f6f9fe +$gt-color-comment-txt := #333333 +$gt-color-link-active := #333333 +$gt-color-btn := #ffffff +$gt-color-popbg := #ffffff +$gt-size-base := 16px // default font-size +$gt-size-border-radius := 5px +$gt-breakpoint-mobile := 479px +$gt-mask-z-index := 9999 + +/* functions & mixins */ +clearfix() { + &:before, + &:after { + content: " "; + display: table; + } + &:after { clear: both; } +} +em($px, $base-size = $gt-size-base) + u = unit($px) + if (u is 'px') + unit($px / $base-size, 'em') + else + unit($px, u) + +mobile() + @media (max-width: $gt-breakpoint-mobile) + {block} + +/* variables - calculated */ +$gt-size-loader-dot := em(6px) +$gt-size-loader := em(28px) +$gt-size-avatar := em(50px) +$gt-size-avatar-mobi := em(32px) + +/* styles */ +// Put everything under container to avoid style conflicts +.gt-container + box-sizing: border-box + padding: 2rem + * + box-sizing: border-box + font-size: $gt-size-base + // common + a + color: $gt-color-main + &:hover + color: lighten($gt-color-main, 20%) + border-color: lighten($gt-color-main, 20%) + &.is--active + color: $gt-color-link-active + cursor: default !important + &:hover + color: $gt-color-link-active + .hide + display: none !important + // icons + .gt-svg + display: inline-block + width: em(16px) + height: em(16px) + vertical-align: sub + svg + width: 100% + height: 100% + fill: $gt-color-main + .gt-ico + display: inline-block + &-text + margin-left: em(5px) + &-github + .gt-svg + width: 100% + height: 100% + svg + fill: inherit + /* loader */ + .gt-spinner + position: relative + &::before + content: '' + box-sizing: border-box + position: absolute + top: 3px + width: em(12px) + height: em(12px) + margin-top: em(-3px) + margin-left: em(-6px) + border-radius: 50% + border: 1px solid $gt-color-btn + border-top-color: $gt-color-main + animation: gt-kf-rotate .6s linear infinite + + .gt-loader + position: relative + border: 1px solid $gt-color-loader + animation: ease gt-kf-rotate 1.5s infinite + display: inline-block + font-style: normal + width: $gt-size-loader + height: $gt-size-loader + //font-size: $gt-size-loader + line-height: $gt-size-loader + border-radius: 50% + &:before + content: '' + position: absolute + display: block + top: 0 + left: 50% + margin-top: -($gt-size-loader-dot / 2) + margin-left: -($gt-size-loader-dot / 2) + width: $gt-size-loader-dot + height: $gt-size-loader-dot + background-color: $gt-color-loader + border-radius: 50% + // avatar + .gt-avatar + display: inline-block + width: $gt-size-avatar + height: $gt-size-avatar + +mobile() + width: $gt-size-avatar-mobi + height: $gt-size-avatar-mobi + img + width: 100% + height: auto + border-radius: 3px + &-github + width: $gt-size-avatar - em(2px) + height: $gt-size-avatar - em(2px) + +mobile() + width: $gt-size-avatar-mobi - em(2px) + height: $gt-size-avatar-mobi - em(2px) + // button + .gt-btn + padding: em(12px) em(20px) + display: inline-block + line-height: 1 + text-decoration: none + white-space: nowrap + cursor: pointer + border: 1px solid $gt-color-main + border-radius: $gt-size-border-radius + background-color: $gt-color-main + color: $gt-color-btn + outline: none + font-size: em(12px) + &-text + font-weight: 400 + &-loading + position: relative + margin-left: em(8px) + display: inline-block + width: em(12px) + height: em(16px) + vertical-align: top + &.is--disable + cursor: not-allowed + opacity: 0.5 + &-login + margin-right: 0 + &-preview + background-color: $gt-color-btn + color: $gt-color-main + &:hover + background-color: darken($gt-color-btn, 5%) + border-color: lighten($gt-color-main, 20%) + &-public + &:hover + background-color: lighten($gt-color-main, 20%) + border-color: lighten($gt-color-main, 20%) + &-loadmore + // loadmore + + /* error */ + .gt-error + text-align: center + margin: em(10px) + color: $gt-color-error + + /* initing */ + .gt-initing + padding: em(20px) 0 + text-align: center + &-text + margin: em(10px) auto + font-size: 92% + + /* no int */ + .gt-no-init + padding: em(20px) 0 + text-align: center + + /* link */ + .gt-link + border-bottom: 1px dotted $gt-color-main + &-counts, &-project + text-decoration: none + + /* meta */ + .gt-meta + margin: em(20px) 0 + padding: em(16px) 0 + position: relative + border-bottom: 1px solid $gt-color-hr + font-size: em(16px) + position: relative + z-index: 10 + clearfix() + + .gt-counts + margin: 0 em(10px) 0 0 + + .gt-user + float: right + margin: 0 + font-size: 92% + &-pic + width: 16px + height: 16px + vertical-align: top + margin-right: em(8px) + &-inner + display: inline-block + cursor: pointer + .gt-ico + margin: 0 0 0 em(5px) + svg + fill: inherit + .is--poping + .gt-ico + svg + fill: $gt-color-main + + .gt-version + color: $gt-color-sub + margin-left: em(6px) + + .gt-copyright + margin: 0 em(15px) em(8px) + border-top: 1px solid $gt-color-hr + padding-top: em(8px) + + /* popup */ + .gt-popup + position: absolute + right: 0 + top: em(38px) + background: $gt-color-popbg + display: inline-block + border: 1px solid $gt-color-hr + padding: em(10px) 0 + font-size: em(14px) + letter-spacing: .5px + .gt-action + cursor: pointer + display: block + margin: em(8px) 0 + padding: 0 em(18px) + position: relative + text-decoration: none + &.is--active + &:before + content: '' + width: em(4px) + height: em(4px) + background: $gt-color-main + position: absolute + left: em(8px) + top: em(7px) + /* header */ + .gt-header + position: relative + display: flex + &-comment + flex: 1 + margin-left: em(20px) + +mobile() + margin-left: em(14px) + &-textarea + padding: em(12px) + display: block + box-sizing: border-box + width: 100% + min-height: em(82px) + max-height: em(240px) + border-radius: $gt-size-border-radius + border: 1px solid $gt-color-input-border + font-size: em(14px) + word-wrap: break-word + resize: vertical + background-color: $gt-color-input-bg + outline: none + transition: all 0.25s ease + &:hover + background-color: lighten($gt-color-input-bg, 50%) + // box-shadow: 0 em(10px) em(60px) 0 $gt-color-input-bg + &-preview + padding: em(12px) + border-radius: $gt-size-border-radius + border: 1px solid $gt-color-input-border + background-color: $gt-color-input-bg + &-controls + position: relative + margin: em(12px) 0 0 + clearfix() + +mobile() + margin: 0 + &-tip + font-size: em(14px) + color: $gt-color-main + text-decoration: none + vertical-align: sub + +mobile() + display: none + .gt-btn + float: right + margin-left: em(20px) + +mobile() + float: none + width: 100% + margin: em(12px) 0 0 + + &:after + content: '' + position: fixed + bottom: 100% + left: 0 + right: 0 + top: 0 + opacity: 0 + &.gt-input-focused + position: relative + &:after + content: '' + position: fixed + bottom: 0% + left: 0 + right: 0 + top: 0 + background: #000 + opacity: 0.6 + transition: opacity .3s, bottom 0s + z-index: $gt-mask-z-index + .gt-header-comment + z-index: $gt-mask-z-index + 1 + + /* comments */ + .gt-comments + padding-top: em(20px) + &-null + text-align: center + &-controls + margin: em(20px) 0 + text-align: center + + /* comment */ + .gt-comment + position: relative + padding: em(10px) 0 + display: flex + &-content + flex: 1 + margin-left: em(20px) + padding: em(12px) em(16px) + background-color: $gt-color-comment-bg + overflow: auto + transition: all ease 0.25s + &:hover + box-shadow: 0 em(10px) em(60px) 0 darken($gt-color-comment-bg, 2%) + +mobile() + margin-left: em(14px) + padding: em(10px) em(12px) + &-header + margin-bottom: em(8px) + font-size: em(14px) + position: relative + &-username + font-weight: 500 + color: $gt-color-main + text-decoration: none + &:hover + text-decoration: underline + &-text + margin-left: em(8px) + color: $gt-color-sub + &-date + margin-left: em(8px) + color: $gt-color-sub + &-like, &-edit, &-reply + position: absolute + height: em(22px) + &:hover + cursor: pointer + &-like + top: 0 + right: em(32px) + &-edit, &-reply + top: 0 + right: 0 + &-body + color: $gt-color-comment-txt !important + &-admin + .gt-comment-content + background-color: $gt-color-comment-adminbg + +@keyframes gt-kf-rotate + 0% + transform: rotate(0) + 100% + transform: rotate(360deg) diff --git a/package/comment/index.vue b/package/comment/index.vue new file mode 100644 index 0000000..2480229 --- /dev/null +++ b/package/comment/index.vue @@ -0,0 +1,321 @@ +