├── .gitignore ├── CHANGELOG.md ├── README.md ├── docs ├── .vuepress │ ├── config.js │ └── public │ │ ├── android-icon-144x144.png │ │ ├── android-icon-192x192.png │ │ ├── android-icon-36x36.png │ │ ├── android-icon-48x48.png │ │ ├── android-icon-72x72.png │ │ ├── android-icon-96x96.png │ │ ├── apple-icon-114x114.png │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-144x144.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-180x180.png │ │ ├── apple-icon-57x57.png │ │ ├── apple-icon-60x60.png │ │ ├── apple-icon-72x72.png │ │ ├── apple-icon-76x76.png │ │ ├── apple-icon-precomposed.png │ │ ├── apple-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── manifest.json │ │ ├── ms-icon-144x144.png │ │ ├── ms-icon-150x150.png │ │ ├── ms-icon-310x310.png │ │ ├── ms-icon-70x70.png │ │ └── vuepress-blog-logo.png ├── README.md └── learn │ └── README.md ├── package.json ├── preview.png ├── src ├── .vuepress │ ├── config.js │ ├── public │ │ ├── android-chrome-192x192.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ ├── site.webmanifest │ │ └── vuepress-blog-logo.png │ └── theme │ │ ├── components │ │ ├── ArchiveList.vue │ │ ├── Blog.vue │ │ ├── Post.vue │ │ ├── PostMeta.vue │ │ ├── PostPreview.vue │ │ └── TagList.vue │ │ ├── index.js │ │ ├── layouts │ │ └── Layout.vue │ │ └── util.js ├── README.md ├── archive │ └── README.md └── blog │ ├── README.md │ ├── my-first-post.md │ ├── my-fourth-post.md │ ├── my-second-post.md │ └── my-third-post.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Project dependencies 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | .cache/ 5 | # Build directory 6 | /public 7 | .DS_Store 8 | yarn-error.log 9 | package-lock.json 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.0.0] 9 | 10 | - Project is now deprecated 11 | - For continued updates on Ben's opinions on a blogging boilerplate, see [Ben's Blog Boilerplate](https://github.com/bencodezen/bens-blog-boilerplate) 12 | 13 | ## [0.11.0-beta] 14 | 15 | ### Added 16 | 17 | - [#41](https://github.com/bencodezen/vuepress-blog-boilerplate/issues/41) Rerelease tagging feature 18 | - [#31](https://github.com/bencodezen/vuepress-blog-boilerplate/issues/31) Add repo config to docs 19 | 20 | ### Changed 21 | 22 | - [#37](https://github.com/bencodezen/vuepress-blog-boilerplate/issues/37) Improved favicon process 23 | - Archived posts page 24 | 25 | ## [0.10.0-beta] 26 | 27 | ### Added 28 | 29 | - Explanation for logos in docs 30 | - Feature to estimate reading time on blog posts 31 | 32 | ## [0.9.0-beta] 33 | 34 | ### Added 35 | 36 | - Configured VuePress Plugin Janitor 37 | 38 | ### Changed 39 | 40 | - Google Analytics is now a plugin and configured via plugin and not in the config object directly 41 | 42 | ### Fixed 43 | 44 | - Future blog posts will no longer be output in the build directory 45 | - RSS Feed filter will now compare identical UTC timezone 46 | 47 | ### Deprecated 48 | 49 | - Limited tagging UI functionality (i.e., sort posts by tags) was removed in order to allow for better integration of official blog plugin in the near future 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VuePress Blog Boilerplate (Deprecated) 2 | 3 | ![Status: Deprecated](https://img.shields.io/badge/status-deprecated-orange) [![Version](https://img.shields.io/badge/version-1.0.0-green.svg)](https://github.com/bencodezen/vuepress-blog-boilerplate/blob/master/CHANGELOG.md) [![Netlify Status](https://api.netlify.com/api/v1/badges/de71217a-c091-4908-a913-d6415815c92d/deploy-status)](https://app.netlify.com/sites/vuepress-blog-boilerplate/deploys) 4 | 5 | This is a **deprecated** opinionated architecture that uses [VuePress v1.0.0-alpha](https://vuepress.vuejs.org) to power a blogging platform. 6 | 7 | ## Purpose 8 | 9 | To provide a blueprint of how blogging is possible with [VuePress v1.0.0-alpha](https://vuepress.vuejs.org/) and empower you with enough boilerplate so you feel comfortable customizing it to your liking. 10 | 11 | ## Why is it deprecated? 12 | 13 | As my work with OSS expands, I wanted the flexibility to switch tech stacks as the requirements for my own blog / site requirements increase. As a result, I do not have the time to maintain this one since it would be a separate effort from my current OSS work. 14 | 15 | To follow the latest, you can follow the new repo at [Ben's Blog Boilerplate](https://github.com/bencodezen/bens-blog-boilerplate). 16 | 17 | ## Features 18 | 19 | - Write posts in Markdown 20 | - Basic pagination sorted by most recent posts 21 | - Ability to search for posts via headings within the post 22 | - Archived posts page 23 | - Basic scheduling for future publishing 24 | - Basic tagging for posts 25 | - Automatic RSS feed generation 26 | - Easily integrate Google Analytics 27 | 28 | ## Getting Started 29 | 30 | ### Prerequisites 31 | 32 | - [NodeJS 12](https://nodejs.org/) 33 | - [yarn](https://yarnpkg.com/lang/en/docs/install/) (Recommended) 34 | - Basic knowledge of navigating the terminal 35 | 36 | ### Installation 37 | 38 | > If your plan is follow the tutorial all the way through to deployment, make sure you fork this project instead of simply cloning it! 39 | 40 | In your terminal, navigate to the desired directory where you want this project to live. 41 | 42 | ```bash 43 | # Clone the repo for local development 44 | git clone https://github.com/bencodezen/vuepress-blog-boilerplate.git 45 | 46 | # Change directory into project 47 | cd vuepress-blog-boilerplate 48 | 49 | # Install dependencies 50 | yarn 51 | 52 | # Run local server 53 | yarn dev 54 | ``` 55 | 56 | You should now be able to visit [http://localhost:8080](http://localhost:8080)! 57 | 58 | ## Documentation 59 | 60 | To check out the tutorial and docs, check out [the guide](https://vuepress-blog-boilerplate.bencodezen.io/). 61 | 62 | ## Examples 63 | 64 | - [VuePress Blog Boilerplate Demo](https://vigorous-lovelace-5c861d.netlify.com/) 65 | - [BenCodeZen Blog](https://www.bencodezen.io) 66 | 67 | ## Hat Tip 68 | 69 | For those familiar with the [Vue.js](https://www.vuejs.org) ecosystem, you might be reminded of [Chris Fritz's](https://www.twitter.com/chrisvfritz) [Vue Enterprise Boilerplate](https://github.com/chrisvfritz/vue-enterprise-boilerplate) and you would be absolutely right. I thought the concept was brilliant and wanted to do something similar for the [VuePress](https://vuepress.vuejs.org) ecosystem since blogging is something that still requires a fair amount of configuration and knowledge in order to get started. 70 | 71 | And in case you didn't know, Chris Fritz is one of the core contributors to the incredible [Vue.js docs](https://vuejs.org/v2/guide/) that we all love so much. So if you would like to help support him so he can spend more time on creating awesome content for the Vue.js community, please support him by [becoming a sponsor on Patreon](https://www.patreon.com/chrisvuefritz). 72 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'VuePress Blog Boilerplate', 3 | dest: './public', 4 | themeConfig: { 5 | repo: 'https://github.com/bencodezen/vuepress-blog-boilerplate', 6 | repoLabel: 'Repo', 7 | docsDir: 'src', 8 | editLinks: true, 9 | logo: '/vuepress-blog-logo.png', 10 | editLinkText: 'Found a bug? Help me improve this page!', 11 | nav: [ 12 | { text: 'Home', link: '/' }, 13 | { text: 'Learn', link: '/learn/' } 14 | ], 15 | version: '0.11.0-beta' 16 | }, 17 | plugins: [ 18 | [ 19 | '@vuepress/google-analytics', 20 | { 21 | ga: 'UA-92645815-3' 22 | } 23 | ], 24 | [ 25 | 'vuepress-plugin-rss', 26 | { 27 | base_url: '/', 28 | site_url: 'https://www.bencodezen.io', 29 | filter: frontmatter => frontmatter.date <= new Date(), 30 | count: 20 31 | } 32 | ] 33 | ], 34 | head: [ 35 | [ 36 | 'link', 37 | { rel: 'apple-touch-icon', sizes: '57x57', href: '/apple-icon-57x57.png' } 38 | ], 39 | [ 40 | 'link', 41 | { rel: 'apple-touch-icon', sizes: '60x60', href: '/apple-icon-60x60.png' } 42 | ], 43 | [ 44 | 'link', 45 | { rel: 'apple-touch-icon', sizes: '72x72', href: '/apple-icon-72x72.png' } 46 | ], 47 | [ 48 | 'link', 49 | { rel: 'apple-touch-icon', sizes: '76x76', href: '/apple-icon-76x76.png' } 50 | ], 51 | [ 52 | 'link', 53 | { 54 | rel: 'apple-touch-icon', 55 | sizes: '114x114', 56 | href: '/apple-icon-114x114.png' 57 | } 58 | ], 59 | [ 60 | 'link', 61 | { 62 | rel: 'apple-touch-icon', 63 | sizes: '120x120', 64 | href: '/apple-icon-120x120.png' 65 | } 66 | ], 67 | [ 68 | 'link', 69 | { 70 | rel: 'apple-touch-icon', 71 | sizes: '144x144', 72 | href: '/apple-icon-144x144.png' 73 | } 74 | ], 75 | [ 76 | 'link', 77 | { 78 | rel: 'apple-touch-icon', 79 | sizes: '152x152', 80 | href: '/apple-icon-152x152.png' 81 | } 82 | ], 83 | [ 84 | 'link', 85 | { 86 | rel: 'apple-touch-icon', 87 | sizes: '180x180', 88 | href: '/apple-icon-180x180.png' 89 | } 90 | ], 91 | [ 92 | 'link', 93 | { 94 | rel: 'icon', 95 | type: 'image/png', 96 | sizes: '192x192', 97 | href: '/android-icon-192x192.png' 98 | } 99 | ], 100 | [ 101 | 'link', 102 | { 103 | rel: 'icon', 104 | type: 'image/png', 105 | sizes: '32x32', 106 | href: '/favicon-32x32.png' 107 | } 108 | ], 109 | [ 110 | 'link', 111 | { 112 | rel: 'icon', 113 | type: 'image/png', 114 | sizes: '96x96', 115 | href: '/favicon-96x96.png' 116 | } 117 | ], 118 | [ 119 | 'link', 120 | { 121 | rel: 'icon', 122 | type: 'image/png', 123 | sizes: '16x16', 124 | href: '/favicon-16x16.png' 125 | } 126 | ], 127 | ['link', { rel: 'manifest', href: '/manifest.json' }], 128 | ['meta', { name: 'msapplication-TileColor', content: '#ffffff' }], 129 | [ 130 | 'meta', 131 | { name: 'msapplication-TileImage', content: '/ms-icon-144x144.png' } 132 | ], 133 | ['meta', { name: 'theme-color', content: '#ffffff' }] 134 | ] 135 | } 136 | -------------------------------------------------------------------------------- /docs/.vuepress/public/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/android-icon-144x144.png -------------------------------------------------------------------------------- /docs/.vuepress/public/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/android-icon-192x192.png -------------------------------------------------------------------------------- /docs/.vuepress/public/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/android-icon-36x36.png -------------------------------------------------------------------------------- /docs/.vuepress/public/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/android-icon-48x48.png -------------------------------------------------------------------------------- /docs/.vuepress/public/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/android-icon-72x72.png -------------------------------------------------------------------------------- /docs/.vuepress/public/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/android-icon-96x96.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-114x114.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-120x120.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-144x144.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-152x152.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-180x180.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-57x57.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-60x60.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-72x72.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-76x76.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon-precomposed.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/apple-icon.png -------------------------------------------------------------------------------- /docs/.vuepress/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/favicon-16x16.png -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/favicon-32x32.png -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/favicon-96x96.png -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /docs/.vuepress/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /docs/.vuepress/public/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/ms-icon-144x144.png -------------------------------------------------------------------------------- /docs/.vuepress/public/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/ms-icon-150x150.png -------------------------------------------------------------------------------- /docs/.vuepress/public/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/ms-icon-310x310.png -------------------------------------------------------------------------------- /docs/.vuepress/public/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/ms-icon-70x70.png -------------------------------------------------------------------------------- /docs/.vuepress/public/vuepress-blog-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/docs/.vuepress/public/vuepress-blog-logo.png -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /vuepress-blog-logo.png 4 | heroText: VuePress Blog Boilerplate 5 | tagline: An ever-evolving and opinionated dev environment for people who want to use VuePress to power their blogs. 6 | actionText: Get Started → 7 | actionLink: /learn/ 8 | features: 9 | - title: Blogger First 10 | details: This framework optimizes the VuePress engine for blogging first and foremost. Includes default features like RSS feed generation, list of recent posts, etc. 11 | - title: Simplicity First 12 | details: Minimal setup with markdown-centered project structure helps you focus on writing. 13 | - title: Vue.js Powered Engine 14 | details: Enjoy the dev experience of Vue + webpack, use Vue components in markdown, and develop your theme with Vue 15 | footer: MIT Licensed | Created by BenCodeZen 16 | --- -------------------------------------------------------------------------------- /docs/learn/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: auto 3 | type: guide 4 | --- 5 | 6 | # VuePress Blog Boilerplate 7 | 8 | ## Introduction 9 | 10 | Welcome to my guide on how to use the [VuePress Blog Boilerplate](https://github.com/bencodezen/vuepress-blog-boilerplate/)! This project was born out of a desire to use [VuePress](https://vuepress.vuejs.org) as my blogging engine and no clear path to do so. 11 | 12 | ### Version 13 | 14 | This project is currently at `version {{ $themeConfig.version }}`. 15 | 16 | ### Purpose 17 | 18 | The objective for this project is simple: 19 | 20 | > To provide a blueprint of how blogging is possible with [VuePress](https://vuepress.vuejs.org/) and empower you with enough boilerplate so you feel comfortable customizing it to your liking. 21 | 22 | ### Features 23 | 24 | By using this boilerplate, in addition to all the awesome things that already come with [VuePress](https://vuepress.vuejs.org), you get the following out of the box: 25 | 26 | - Automatically generated recent posts on the home page 27 | - Simple Google Analytics configuration 28 | - Automatic RSS Feed Generation 29 | - Easy favicon configuration 30 | - Simple pagination on the home page 31 | - Archived page of all posts sorted by date 32 | 33 | Piece of cake right? Well time's a wasting, let's get to it! 34 | 35 | ## Getting Started 36 | 37 | ### Prerequisites 38 | 39 | - [NodeJS >= 8](https://nodejs.org/) 40 | - [yarn](https://yarnpkg.com/lang/en/docs/install/) (Optional) 41 | - Basic knowledge of navigating the terminal 42 | 43 | ### Installation 44 | 45 | ::: tip 46 | If your plan is follow the tutorial all the way through to deployment, make sure you fork this project instead of simply cloning it! 47 | ::: 48 | 49 | In your terminal, navigate to the desired directory where you want this project to live. 50 | 51 | ```bash 52 | # Clone the repo for local development 53 | git clone https://github.com/bencodezen/vuepress-blog-boilerplate.git 54 | 55 | # Change directory into project 56 | cd vuepress-blog-boilerplate 57 | 58 | # Install dependencies 59 | npm install # or yarn 60 | 61 | # Run local server 62 | npm run dev # OR yarn dev 63 | ``` 64 | 65 | You should now be able to visit [http://localhost:8080](http://localhost:8080) to see your local server running! 66 | 67 | ## Tutorial 68 | 69 | ### Step 1: Create your first blog post 70 | 71 | 1. Create a new file called `my-fourth-post.md` in the `./src/blog` directory 72 | 73 | Your file explorer should now look like this: 74 | 75 | ```{8} 76 | . 77 | ├── src 78 | │ └── .vuepress 79 | │ └── blog 80 | │ └── my-first-post.md 81 | │ └── my-second-post.md 82 | │ └── my-third-post.md 83 | │ └── my-fourth-post.md 84 | │ └── README.md 85 | ├── .gitignore 86 | ├── package.json 87 | ├── README.md 88 | ``` 89 | 90 | 2. Open `my-fourth-post.md` in your favorite text editor. 91 | 92 | 3. Copy and paste the following into your markdown file: 93 | 94 | ``` 95 | --- 96 | title: My Fourth Post 97 | date: 2018-12-28 17:22:00 98 | type: post 99 | blog: true 100 | excerpt: I'm creating my first post! 101 | tags: 102 | - Blogging 103 | - Tutorials 104 | --- 105 | 106 | # Hello! 107 | 108 | This is pretty awesome! 109 | 110 | ::: tip 111 | Aren't custom containers cool? 112 | ::: 113 | 114 | > "I have a big head... and little arms." - T-rex from 'Meet the Robinsons' 115 | ``` 116 | 117 | 4. Once you save, you should now see the fourth post show up on your home page! 118 | 119 | 5. :confetti_ball: You now have the ability to write all the blog posts you want! 120 | 121 | ### Step 2: Create a new page 122 | 123 | Odds are you will also want to customize the top navigation with a couple pages of your own to personalize the site. So let's create a simple About page together! 124 | 125 | 1. Create a new directory in the `./src` directory called `about` 126 | 127 | 2. Create a new file in the new `about` directory called `README.md` 128 | 129 | ```{4-5} 130 | . 131 | ├── src 132 | │ └── .vuepress 133 | │ └── about 134 | │ └── README.md 135 | │ └── blog 136 | │ └── README.md 137 | ├── .gitignore 138 | ├── package.json 139 | ├── README.md 140 | ``` 141 | 142 | ::: tip 143 | `README.md` files in directories get converted to `index.html` files at build time so don't worry if it looks weird right now. 144 | ::: 145 | 146 | 3. Let's just paste something simple in the `about/README.md` file. 147 | 148 | ```md 149 | # About Me 150 | 151 | I'm a great developer who has a lot of great things to share with the world. 152 | Can't wait to start writing more about topics I love and am passionate about! 153 | ``` 154 | 155 | 4. Now if you visit, `http://localhost:8080/about/`, you should see your page! 156 | 157 | ::: warning 158 | If you are getting an error, just restart your VuePress server and everything should be good to go! 159 | ::: 160 | 161 | ### Step 3: Add an item to the main nav 162 | 163 | 1. Open the `./src/.vuepress/config.js` file in your favorite text editor 164 | 165 | 2. Locate the section under `themeConfig.nav`: 166 | 167 | ```js{12-15} 168 | module.exports = { 169 | title: 'My New VuePress Blog', 170 | ga: '', 171 | dest: './public', 172 | themeConfig: { 173 | repo: 'https://wwww.github.com', 174 | repoLabel: 'Custom Repo Label', 175 | docsDir: 'src', 176 | logo: '/vuepress-blog-logo.png', 177 | editLinks: true, 178 | editLinkText: 'Found a bug? Help me improve this page!', 179 | nav: [ 180 | { text: 'Home', link: '/' }, 181 | { text: 'RSS Feed', link: '/rss.xml' } 182 | ] 183 | }, 184 | ``` 185 | 186 | 3. Add a new object for the About page to the `nav` array like this: 187 | 188 | ```js{14} 189 | module.exports = { 190 | title: 'My New VuePress Blog', 191 | ga: '', 192 | dest: './public', 193 | themeConfig: { 194 | repo: 'https://wwww.github.com', 195 | repoLabel: 'Custom Repo Label', 196 | docsDir: 'src', 197 | logo: '/vuepress-blog-logo.png', 198 | editLinks: true, 199 | editLinkText: 'Found a bug? Help me improve this page!', 200 | nav: [ 201 | { text: 'Home', link: '/' }, 202 | { text: 'About', link: '/about/' }, 203 | { text: 'RSS Feed', link: '/rss.xml' } 204 | ] 205 | }, 206 | ``` 207 | 208 | ::: warning 209 | When you want the user to go to the `index.html` of a directory, it's critical that you put a `/` at the end of the relative link because it will break otherwise. 210 | 211 | ```js 212 | // Good 213 | { text: 'About', link: '/about/' } 214 | 215 | // Bad 216 | { text: 'About', link: '/about' } 217 | ``` 218 | 219 | ::: 220 | 221 | ### Step 4: Deploy your site with Netlify 222 | 223 | 1. Create an account with Netlify if you don't already have one 224 | 1. Click `New site from Git` in the upper right corner 225 | 1. Choose your Git provider 226 | 1. Authorize Netlify to have access to your repos 227 | 1. Choose your VuePress blog repo 228 | 1. Configure the following "Build & deploy" settings: 229 | - **Branch to deploy**s: master 230 | - **Build command**: `npm run build` 231 | - **Publish directory**: `public/` 232 | 1. Click `Deploy site` 233 | 234 | Now your site will automatically deploy whenever you push updates to your repo! 235 | 236 | ### Step 5: You're ready to go! 237 | 238 | You have gone from zero to one! Congratulations! :confetti_ball: 239 | 240 | For more advanced blogging features, be sure to check out the rest of the docs! 241 | 242 | ## Blogging Features 243 | 244 | ### Tags 245 | 246 | You can tag your posts in frontmatter now. 247 | 248 | Here is an example from: 249 | 250 | ```yaml 251 | --- 252 | title: A Post with Tags 253 | ... 254 | tags: 255 | - HTML 256 | - CSS 257 | - JavaScript 258 | --- 259 | ``` 260 | 261 | This will expose your tags in the `BlogPostList.vue` where it will filter down the displayed posts based on what tags are selected. Users will also be able to clear out the tags whenever tags are selected. 262 | 263 | ### Scheduling Posts for Future "Publishing" 264 | 265 | This blog theme currently allows you to set future "publish"\* dates via [ISO-8601 format](https://en.wikipedia.org/wiki/ISO_8601) (i.e., YYYY-MM-DD hh:mm:ss). 266 | 267 | ### Time to Read Snippet 268 | 269 | This is currently supported through [Darren Jenning's VuePress Plugin Reading Time](https://github.com/darrenjennings/vuepress-plugin-reading-time) and is configured in the `plugin` section of `config.js`. 270 | 271 | You can find examples of its usage in `BlogPostPreview.vue` and `BlogPostList.vue` with snippets such as: 272 | 273 | ```js 274 | $page.readingTime.text 275 | ``` 276 | 277 | You can find more info about [options of the reading time plugin here](https://github.com/darrenjennings/vuepress-plugin-reading-time#usage). 278 | 279 | ## Site Configuration 280 | 281 | Like any other site, there's still some manual configuration to do to add the final touches to your site. Here are the most important configurations you should consider for you blog: 282 | 283 | ### Site & Page Title 284 | 285 | On most pages, VuePress uses the following pattern: 286 | 287 | ```html 288 | {{ $page.title }} | {{ $siteTitle }} 289 | ``` 290 | 291 | #### Site Title 292 | 293 | This attribute configures the site `` element on your site. It is what determines `$siteTitle` in the VuePress app. 294 | 295 | 1. Open `./src/.vuepress/config.js` in your favorite text editor 296 | 297 | ```js{2} 298 | module.exports = { 299 | title: 'My New VuePress Blog', 300 | ... 301 | } 302 | ``` 303 | 304 | 2. Update the `title` key with your desired site title as a string 305 | 306 | #### Page Title 307 | 308 | By default, VuePress will assume that the h1 in your markdown file is the page title that you want: 309 | 310 | ```md 311 | # My Heading One That Turns into a Title 312 | ``` 313 | 314 | If you would like to explicitly set a specific page title, use frontmatter to overwrite the default. 315 | 316 | ``` 317 | --- 318 | title: My Custom Title to Override the H1 319 | --- 320 | 321 | # My Heading One That Turns into a Title 322 | ``` 323 | 324 | ### Logo 325 | 326 | The logo is responsible for the image that you see in the upper left corner. The default is for it to be blank, but I found that most people like having their logo on their site and have turned included it in the boilerplate. 327 | 328 | In order to update your logo, this is controlled in your `config.js` file under the `themeConfig` key: 329 | 330 | ```js{6} 331 | module.exports = { 332 | title: 'My New VuePress Blog', 333 | dest: './public', 334 | themeConfig: { 335 | ..., 336 | logo: '/vuepress-blog-logo.png' 337 | } 338 | } 339 | ``` 340 | 341 | The path to your image is based on `.vuepress/public` directory. 342 | 343 | #### Examples 344 | 345 | ```js 346 | // If your logo is in `.vuepress/public/your-logo.png` 347 | module.exports = { 348 | themeConfig: { 349 | logo: '/your-logo.png' 350 | } 351 | } 352 | 353 | // If your logo is in `.vuepress/public/img/your-logo.png` 354 | module.exports = { 355 | themeConfig: { 356 | logo: '/img/your-logo.png' 357 | } 358 | } 359 | ``` 360 | 361 | ### Repo 362 | 363 | This is your repo link that will be used for features like [Edit this page](https://v1.vuepress.vuejs.org/theme/default-theme-config.html#git-repo-and-edit-links) and the repo link. 364 | 365 | This is controlled in `.vuepress/config.js` under `themeConfig`. 366 | 367 | ```js 368 | module.exports = { 369 | ... 370 | themeConfig: { 371 | ... 372 | // The URL used for generating any features related to the URL 373 | repo: 'https://wwww.github.com', 374 | // The label that is used in the top nav 375 | repoLabel: 'Repo', 376 | ... 377 | } 378 | } 379 | ``` 380 | 381 | ### Google Analytics 382 | 383 | Odds are pretty good that you'd like to do some metric tracking for visitors to your blog and such. So luckily it's really easy to add your Google Analytics ID: 384 | 385 | 1. Login to your Google Analytics account 386 | 2. Create a new account for your site if you haven't already 387 | 3. Visit the `Admin` section of your account 388 | 4. Look for `Tracking Info` > `Tracking Code` 389 | 5. Copy the tracking ID to the clipboard: 390 | - It should look something like `UA-12345678-1` 391 | 6. Open `./src/.vuepress/config.js` in your favorite text editor 392 | 7. Update the `ga` property with your tracking ID 393 | 394 | ```js{4-9} 395 | module.exports = { 396 | title: 'My New VuePress Blog', 397 | ... 398 | plugins: [ 399 | '@vuepress/google-analytics', 400 | { 401 | 'ga': '' // UA-00000000-0 402 | } 403 | ] 404 | ... 405 | } 406 | ``` 407 | 408 | ### Favicon 409 | 410 | Everything is already configured for favicons on your page. All you need to do is: 411 | 412 | 1. Use [Favicon Generator](https://realfavicongenerator.net/) to generate the appropriate files for favicons 413 | - And if you're wondering, I just used the default settings for this boilerplate 414 | 2. Replace all the files in `./src/.vuepress/public` and your favicon should just show up! 415 | 416 | ::: tip 417 | In the event you need to manually modify the HTML elements related to favicon can be found in `./src/.vuepress/config.js` under the `head` key. 418 | 419 | ```js 420 | module.exports = { 421 | head: [ 422 | ['link', { rel: 'icon', sizes: '32x32', href: '/favicon-32x32.png' }], 423 | ... 424 | ] 425 | } 426 | ``` 427 | 428 | ::: 429 | 430 | ### Site Repository 431 | 432 | Given that VuePress is designed to optimize for technical documentation, the default theme has a priority to make the repository as easy to as access as possible. 433 | 434 | #### Site Nav Item 435 | 436 | When you start up your environment, you'll notice that the main navigation has the repo in the upper right. It's configured in `./src/.vuepress/config.js`: 437 | 438 | ```js{6-7} 439 | module.exports = { 440 | title: 'My New VuePress Blog', 441 | ga: '', 442 | dest: './public', 443 | themeConfig: { 444 | repo: 'https://wwww.github.com', 445 | repoLabel: 'Repo' 446 | }, 447 | ... 448 | } 449 | ``` 450 | 451 | - `themeConfig.repo`: This is the link that (1) determines whether the nav item displays and (2) Serves as the foundation for the Edit Links feature. 452 | - `themeConfig.repoLabel`: The default is the hosting provider (i.e., GitLab or GitHub), but I think that's misleading from a semantic perspective since the user is going to your repo and not the company site "GitLab" or "GitHub". So I'm recommending "Repo" as an alternative. 453 | 454 | #### Edit Links 455 | 456 | Another great feature about VuePress is the ability to allow users to easily submit PRs to the page. That is configured here in `./src/.vuepress/config.js`: 457 | 458 | ```js{8-9} 459 | module.exports = { 460 | title: 'My New VuePress Blog', 461 | ga: '', 462 | dest: './public', 463 | themeConfig: { 464 | repo: 'https://wwww.github.com', 465 | repoLabel: 'Repo', 466 | editLinks: true, 467 | editLinkText: 'Found a bug? Help me improve this page!', 468 | }, 469 | ... 470 | } 471 | ``` 472 | 473 | - `themeConfig.editLinks`: This is turned off by default, but by adding this in with the boolean of true, it will generate the default text of "Edit this page" on the bottom of each page 474 | - `themeConfig.editLinkText`: This override the default text generated by `themeConfig.editLinks` 475 | 476 | ::: warning 477 | If the `themeConfig.repo` is not filled in, this feature will not work since it uses the repo link to generate the proper links. 478 | ::: 479 | 480 | ## Architecture 481 | 482 | ### How BlogPostList generates the posts 483 | 484 | All blog posts are currently expected to live in the `./src/blog` directory in order for the `<BlogPostList />` component to render the preview of your post. 485 | 486 | ```{5-8} 487 | . 488 | ├── src 489 | │ └── .vuepress 490 | │ └── about 491 | │ └── blog 492 | │ └── my-first-post.md 493 | │ └── my-second-post.md 494 | │ └── my-third-post.md 495 | │ └── contact 496 | │ └── README.md 497 | ├── .gitignore 498 | ├── package.json 499 | ├── README.md 500 | ``` 501 | 502 | ::: tip 503 | In case you're wondering, `<BlogPostList />` will automatically be sorted by date when the list is generated. 504 | ::: 505 | 506 | ### How the RSS feed is generated 507 | 508 | The `rss.xml` file being generated at build time uses the [VuePress Plugin RSS](https://github.com/youngtailors/vuepress-plugin-rss) repo. Although not clear on the site, the determining factor for what determines whether something is added to the RSS feed is whether there is a frontend matter property of `type` with the value `post`. 509 | 510 | ```yaml 511 | # This will get picked up by the RSS plugin 512 | --- 513 | title: The Post I Want to Add to RSS 514 | type: post 515 | --- 516 | # This will not get picked up by the RSS plugin 517 | --- 518 | title: The Post I Want to Add to RSS But Will Be Missing 519 | --- 520 | 521 | ``` 522 | 523 | In addition, I've configured an additional filter to check for posts that are set to be published in the future. After all, wouldn't that be weird to get a post in your RSS feed update that has a date in the future? :laughing: 524 | 525 | ## FAQs 526 | 527 | ### Why VuePress? 528 | 529 | Many are probably wondering why use VuePress? After all, there are a ton of tools out there already built and optimized for blogging aren't there? And you would be absolutely correct! But in my work with VuePress so far, I found the engine quite powerful and intuitive to how I wanted to be able to write: 530 | 531 | - Static site generator 532 | - Minimal setup with markdown-centered project to help you focus on writing 533 | - YAML Frontmatter for managing page specific meta data 534 | - Use Vue components in your markdown content 535 | - Develop custom themes with Vue 536 | - Integrated markdown extensions that make the writing experience even easier. Examples include: 537 | - [Custom containers](https://vuepress.vuejs.org/guide/markdown.html#custom-containers) 538 | - [Code syntax highlighting](https://vuepress.vuejs.org/guide/markdown.html#line-highlighting-in-code-blocks) 539 | 540 | And since it seemed like this was something other people wanted to do, I figured I would go ahead and see how far I could take this. 541 | 542 | ### Why is this separate from the VuePress repo? 543 | 544 | As many of you might have experienced in the past, getting PRs reviewed and integrated into an existing open-source project is fairly difficult to do. And this is by no means the fault of the maintainers, there's just a lot going on and people clamoring for attention. So rather than have my work hidden from the world while it sits in a PR, my goal is to: 545 | 546 | 1. Keep updating this repo and guide so that people can make use of it and create content 547 | 2. Integrate the latest changes from the core VuePress repo in order to ensure the work is not duplicated 548 | 3. Submit PRs until this page will become an artifact of the past and everything will live in the core VuePress docs itself. 549 | 550 | ### What if I want my home page to be my blog posts rather than the static content? 551 | 552 | The page that shows up on the home page (`/src/README.md`) is controlled via its `home` boolean property in the frontmatter. To add in your list of blog posts, you just need to delete this property and copy in the `BlogPostList` component. 553 | 554 | ``` 555 | <BlogPostList 556 | :pages="$site.pages" 557 | :page-size="$site.themeConfig.pageSize" 558 | :start-page="$site.themeConfig.startPage" 559 | /> 560 | ``` 561 | 562 | From there, you can customize the page as you see fit! 563 | 564 | ::: tip Note 565 | This is not a perfect solution from a configuration standpoint, but I will make sure this is easier in future interations on the boilerplate! 566 | ::: 567 | 568 | ## Hat Tip 569 | 570 | For those familiar with the [Vue.js](https://www.vuejs.org) ecosystem, you might be reminded of [Chris Fritz's](https://www.twitter.com/chrisvfritz) [Vue Enterprise Boilerplate](https://github.com/chrisvfritz/vue-enterprise-boilerplate) and you would be absolutely right. I thought the concept was brilliant and wanted to do something similar for the [VuePress](https://vuepress.vuejs.org) ecosystem since blogging is something that still requires a fair amount of configuration and knowledge in order to get started. 571 | 572 | And in case you didn't know, Chris Fritz is one of the core contributors to the incredible [Vue.js docs](https://vuejs.org/v2/guide/) that we are love so much. So if you would like to help support him so he can spend more time on creating awesome content for the Vue.js community, please support him by [becoming a sponsor on Patreon](https://www.patreon.com/chrisvuefritz). 573 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuepress-blog", 3 | "description": "This is a basic template to use VuePress as your blogging platform", 4 | "version": "0.11.0-beta", 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "vuepress dev src", 8 | "dev:docs": "vuepress dev docs", 9 | "build": "vuepress build src", 10 | "build:docs": "vuepress build docs" 11 | }, 12 | "dependencies": {}, 13 | "devDependencies": { 14 | "@vuepress/plugin-google-analytics": "~1.0.0-alpha.0", 15 | "@vuepress/theme-default": "^1.2.0", 16 | "vuepress": "~1.0.0-alpha.30", 17 | "vuepress-plugin-janitor": "1.0.0", 18 | "vuepress-plugin-reading-time": "0.1.1", 19 | "vuepress-plugin-rss": "2.0.0", 20 | "yaml-front-matter": "4.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/preview.png -------------------------------------------------------------------------------- /src/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | const currentDateUTC = new Date().toUTCString() 2 | 3 | module.exports = { 4 | title: 'My New VuePress Blog', 5 | dest: './public', 6 | themeConfig: { 7 | repo: 'https://wwww.github.com', 8 | repoLabel: 'Repo', 9 | editLinks: true, 10 | editLinkText: 'Found a bug? Help me improve this page!', 11 | nav: [ 12 | { text: 'Home', link: '/' }, 13 | { text: 'Blog', link: '/blog/' }, 14 | { text: 'Archive', link: '/archive/' }, 15 | { text: 'RSS Feed', link: '/rss.xml' } 16 | ], 17 | logo: '/vuepress-blog-logo.png', 18 | docsDir: 'src', 19 | pageSize: 5, 20 | startPage: 0, 21 | lastUpdated: 'Last updated' 22 | }, 23 | plugins: [ 24 | [ 25 | '@vuepress/google-analytics', 26 | { 27 | ga: '' // UA-00000000-0 28 | } 29 | ], 30 | [ 31 | 'vuepress-plugin-rss', 32 | { 33 | base_url: '/', 34 | site_url: 'https://vuepressblog.org', 35 | filter: frontmatter => frontmatter.date <= new Date(currentDateUTC), 36 | count: 20 37 | } 38 | ], 39 | 'vuepress-plugin-reading-time', 40 | 'vuepress-plugin-janitor' 41 | ], 42 | head: [ 43 | ['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-icon.png' }], 44 | ['link', { rel: 'icon', sizes: '32x32', href: '/favicon-32x32.png' }], 45 | ['link', { rel: 'icon', sizes: '16x16', href: '/favicon-16x16.png' }], 46 | ['link', { rel: 'manifest', href: '/site.webmanifest' }], 47 | ['link', { rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#5bbad5' }], 48 | ['meta', { name: 'msapplication-TileColor', content: '#da532c' }], 49 | ['meta', { name: 'theme-color', content: '#ffffff' }] 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/.vuepress/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/src/.vuepress/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/.vuepress/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/src/.vuepress/public/apple-touch-icon.png -------------------------------------------------------------------------------- /src/.vuepress/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <browserconfig> 3 | <msapplication> 4 | <tile> 5 | <square150x150logo src="/mstile-150x150.png"/> 6 | <TileColor>#da532c</TileColor> 7 | </tile> 8 | </msapplication> 9 | </browserconfig> 10 | -------------------------------------------------------------------------------- /src/.vuepress/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/src/.vuepress/public/favicon-16x16.png -------------------------------------------------------------------------------- /src/.vuepress/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/src/.vuepress/public/favicon-32x32.png -------------------------------------------------------------------------------- /src/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/src/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /src/.vuepress/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/src/.vuepress/public/mstile-150x150.png -------------------------------------------------------------------------------- /src/.vuepress/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" standalone="no"?> 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" 3 | "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> 4 | <svg version="1.0" xmlns="http://www.w3.org/2000/svg" 5 | width="245.000000pt" height="245.000000pt" viewBox="0 0 245.000000 245.000000" 6 | preserveAspectRatio="xMidYMid meet"> 7 | <metadata> 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | </metadata> 10 | <g transform="translate(0.000000,245.000000) scale(0.100000,-0.100000)" 11 | fill="#000000" stroke="none"> 12 | <path d="M1540 2349 c-25 -4 -61 -9 -80 -12 -19 -3 -62 -9 -95 -12 -33 -4 -62 13 | -9 -65 -10 -3 -2 -30 -6 -60 -10 -30 -3 -62 -8 -70 -10 -8 -2 -40 -7 -70 -10 14 | -30 -3 -66 -8 -80 -10 -40 -5 -113 -17 -134 -21 -10 -2 -44 -6 -75 -9 -31 -4 15 | -63 -8 -71 -10 -8 -2 -37 -7 -65 -11 -27 -3 -57 -8 -65 -9 -9 -2 -43 -6 -75 16 | -10 -80 -9 -105 -26 -105 -70 0 -18 4 -36 8 -39 10 -6 14 -66 5 -67 -99 -11 17 | -123 -19 -134 -46 -5 -14 -6 -35 -2 -47 3 -11 7 -28 8 -36 2 -8 6 -23 10 -32 18 | 5 -13 0 -20 -19 -28 -15 -5 -30 -9 -34 -8 -24 6 -73 -16 -77 -35 -7 -32 -1 19 | -97 9 -97 5 0 2 -8 -5 -17 -8 -9 -9 -14 -3 -11 6 4 15 -10 21 -33 6 -22 6 -42 20 | 2 -45 -5 -3 -2 -12 6 -21 13 -12 13 -18 3 -31 -9 -11 -9 -14 -1 -9 8 5 14 -5 21 | 19 -30 3 -21 12 -60 19 -88 7 -27 11 -58 11 -70 -1 -11 1 -22 5 -25 3 -3 9 22 | -20 12 -38 5 -24 3 -31 -6 -27 -6 4 -3 -1 7 -9 11 -9 17 -16 14 -16 -3 0 1 23 | -26 9 -57 8 -31 16 -76 17 -99 2 -23 6 -46 10 -53 7 -11 13 -33 23 -86 9 -48 24 | 11 -60 14 -60 1 0 3 -9 5 -20 1 -11 10 -51 19 -90 9 -38 20 -92 25 -119 6 -27 25 | 12 -52 15 -54 3 -3 -1 -12 -8 -20 -9 -13 -9 -14 0 -8 9 5 13 -1 14 -21 1 -15 26 | 1 -31 0 -35 0 -5 3 -8 7 -8 4 0 9 -14 10 -31 3 -44 6 -53 17 -62 6 -4 5 -10 27 | -3 -16 -11 -6 -10 -10 5 -15 17 -7 17 -8 -2 -23 -15 -12 -16 -14 -3 -10 16 6 28 | 22 -13 33 -93 5 -38 26 -70 50 -76 22 -5 24 -9 13 -23 -12 -15 -11 -15 7 0 11 29 | 9 34 19 50 23 17 3 44 8 60 11 38 8 60 13 60 15 0 1 9 3 20 5 52 9 95 18 149 30 | 30 44 10 64 11 73 3 10 -8 12 -7 8 4 -5 12 4 17 40 23 56 8 120 22 120 25 0 3 31 | 51 10 87 12 20 1 34 6 32 10 -5 7 15 11 59 11 9 0 17 3 17 8 0 4 9 6 20 4 11 32 | -2 22 0 25 6 10 16 25 10 19 -9 -5 -14 -4 -15 5 -2 6 8 14 15 18 16 4 0 15 4 33 | 23 7 8 4 31 8 50 9 19 2 38 7 41 11 4 5 29 10 57 10 27 1 48 5 45 10 -2 4 5 7 34 | 17 7 13 0 19 -4 14 -12 -4 -7 -3 -8 5 -4 6 4 9 11 6 16 -8 13 18 18 39 8 12 35 | -7 16 -7 12 0 -3 5 7 12 22 15 15 3 30 0 34 -6 5 -8 8 -7 8 3 0 10 11 17 31 36 | 18 18 1 44 10 59 19 32 18 33 44 7 132 -16 52 -16 53 3 48 11 -3 20 -1 20 3 0 37 | 8 73 26 97 24 15 -2 10 90 -8 133 -7 18 -11 35 -7 38 5 5 53 15 93 18 11 1 28 38 | 12 38 24 21 26 19 50 -13 138 -39 105 -110 308 -131 370 -11 33 -24 69 -28 80 39 | -16 36 -71 201 -71 213 0 6 8 19 18 29 78 78 210 250 257 336 61 111 31 124 40 | -100 42 -66 -41 -183 -130 -231 -175 -20 -19 -22 -19 -29 -3 -21 54 -135 383 41 | -135 391 0 5 -11 21 -25 36 -25 27 -34 29 -125 15z"/> 42 | <path d="M187 1629 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/> 43 | <path d="M286 1132 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4 44 | -9 -8z"/> 45 | <path d="M1590 329 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 46 | -5 -10 -11z"/> 47 | <path d="M1316 311 c-3 -5 2 -15 12 -22 15 -12 16 -12 5 2 -7 9 -10 19 -6 22 48 | 3 4 4 7 0 7 -3 0 -8 -4 -11 -9z"/> 49 | <path d="M1365 310 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/> 50 | <path d="M1210 275 c0 -7 30 -13 34 -7 3 4 -4 9 -15 9 -10 1 -19 0 -19 -2z"/> 51 | </g> 52 | </svg> 53 | -------------------------------------------------------------------------------- /src/.vuepress/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "theme_color": "#ffffff", 12 | "background_color": "#ffffff", 13 | "display": "standalone" 14 | } 15 | -------------------------------------------------------------------------------- /src/.vuepress/public/vuepress-blog-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bencodezen/vuepress-blog-boilerplate/c7c5fd9a2e26e055c4c580acb438b51dab89b405/src/.vuepress/public/vuepress-blog-logo.png -------------------------------------------------------------------------------- /src/.vuepress/theme/components/ArchiveList.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="theme-default-content"> 3 | <div v-for="year in getYears()"> 4 | <div class="archive-year">{{year}}</div> 5 | <div v-for="month in getMonths(year)"> 6 | <div class="archive-month">{{month | monthToLongName}}</div> 7 | <ul class="archive-list"> 8 | <li v-for="(item, index) in postsByDate(year, month)" class="archive-list__item"> 9 | {{new Date(item.frontmatter.date).getDate()}} - <router-link :to="item.path">{{item.title}}</router-link> 10 | </li> 11 | </ul> 12 | </div> 13 | </div> 14 | </div> 15 | </template> 16 | 17 | <script> 18 | export default { 19 | name: 'ArchiveList', 20 | props: { 21 | pages: { 22 | type: Array, 23 | default: [] 24 | }, 25 | }, 26 | data() { 27 | return { 28 | selectedTags: [] 29 | } 30 | }, 31 | computed: { 32 | filteredList() { 33 | if (this.pages) { 34 | 35 | return this.pages.filter(item => { 36 | const isBlogPost = !!item.frontmatter.blog 37 | const isReadyToPublish = new Date(item.frontmatter.date) <= new Date() 38 | // check for locales 39 | let isCurrentLocale = true; 40 | if(this.$site.locales) { 41 | const localePath = this.$route.path.split('/')[1] || ""; 42 | isCurrentLocale = item.relativePath.startsWith(localePath); 43 | } 44 | // check if tags contain any of the selected tags 45 | // const hasTags = item.frontmatter.tags && item.frontmatter.tags.some(tag => this.selectedTags.includes(tag)) 46 | // check if tags contain all of the selected tags 47 | const hasTags = !!item.frontmatter.tags && this.selectedTags.every((tag) => item.frontmatter.tags.includes(tag)) 48 | 49 | if (!isBlogPost || !isReadyToPublish || (this.selectedTags.length > 0 && !hasTags) || !isCurrentLocale){ 50 | return false 51 | } 52 | 53 | return true 54 | 55 | }) 56 | .sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date)) 57 | } 58 | }, 59 | }, 60 | methods: { 61 | getYears: function() { 62 | return [...new Set(this.filteredList.map(item => new Date(item.frontmatter.date).getFullYear()))] 63 | }, 64 | 65 | getMonths: function(year) { 66 | return [...new Set(this.filteredList.filter(item => new Date(item.frontmatter.date).getFullYear() == year).map( item => new Date(item.frontmatter.date).getMonth()))] 67 | }, 68 | 69 | postsByDate(year, month) { 70 | return this.filteredList.filter(item => { 71 | const date = new Date(item.frontmatter.date) 72 | return date.getFullYear() == year && date.getMonth() == month 73 | }) 74 | }, 75 | }, 76 | filters: { 77 | // Filter definitions 78 | monthToLongName(value) { 79 | const months = [ "January", "February", "March", "April", "May", "June", 80 | "July", "August", "September", "October", "November", "December" ]; 81 | 82 | return months[value] 83 | } 84 | } 85 | } 86 | </script> 87 | 88 | <style> 89 | .archive-list { 90 | padding: 0; 91 | margin: 0; 92 | } 93 | 94 | .archive-year { 95 | font-weight: bold; 96 | font-size: 32px; 97 | margin: 0 0 16px 0; 98 | } 99 | 100 | .archive-month { 101 | font-weight: bold; 102 | font-size: 24px; 103 | margin: 0 0 16px 16px; 104 | } 105 | 106 | .archive-list__item { 107 | list-style-type: none; 108 | margin: 0 0 16px 32px; 109 | } 110 | </style> 111 | -------------------------------------------------------------------------------- /src/.vuepress/theme/components/Blog.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="theme-default-content"> 3 | <div 4 | v-if="selectedTags.length > 0" 5 | class="filtered-heading" 6 | > 7 | <h2>Filtered by {{ selectedTags.join(',') }}</h2> 8 | <button 9 | type="button" 10 | @click="resetTags" 11 | class="btn clear-filter-btn" 12 | > 13 | Clear filter 14 | </button> 15 | </div> 16 | <ul class="blog-list"> 17 | <li v-for="(item, index) in filteredList" 18 | class="blog-list__item"> 19 | <PostPreview v-on:add-tag="addTag($event)" 20 | v-show="index >= currentPage * pageSize && index < (currentPage + 1) * pageSize" 21 | :item="item" 22 | v-on:tag-click="console.log($event)" 23 | /> 24 | </li> 25 | </ul> 26 | 27 | <div class="pagination"> 28 | <div> 29 | <a href="#" 30 | v-show="currentPage > 0" 31 | @click="previousPage" 32 | class="button--pagination" 33 | > 34 | Newer Posts 35 | </a> 36 | </div> 37 | <div> 38 | <a href="#" 39 | v-show="currentPage < totalPages - 1" 40 | @click="nextPage" 41 | class="button--pagination" 42 | > 43 | Older Posts 44 | </a> 45 | </div> 46 | </div> 47 | </div> 48 | </template> 49 | 50 | <script> 51 | import PostPreview from './PostPreview' 52 | 53 | export default { 54 | components: { PostPreview }, 55 | props: { 56 | pages: { 57 | type: Array, 58 | default: [] 59 | }, 60 | pageSize: { 61 | type: Number, 62 | default: 5 63 | }, 64 | startPage: { 65 | type: Number, 66 | default: 0 67 | } 68 | }, 69 | data() { 70 | return { 71 | currentPage: Math.ceil(this.startPage / this.pageSize), 72 | selectedTags: [] 73 | } 74 | }, 75 | computed: { 76 | filteredList() { 77 | if (this.pages) { 78 | 79 | return this.pages.filter(item => { 80 | const isBlogPost = !!item.frontmatter.blog 81 | const isReadyToPublish = new Date(item.frontmatter.date) <= new Date() 82 | // check for locales 83 | let isCurrentLocale = true; 84 | if(this.$site.locales) { 85 | const localePath = this.$route.path.split('/')[1] || ""; 86 | isCurrentLocale = item.relativePath.startsWith(localePath); 87 | } 88 | // check if tags contain all of the selected tags 89 | const hasTags = !!item.frontmatter.tags && this.selectedTags.every((tag) => item.frontmatter.tags.includes(tag)) 90 | 91 | if (!isBlogPost || !isReadyToPublish || (this.selectedTags.length > 0 && !hasTags) || !isCurrentLocale){ 92 | return false 93 | } 94 | 95 | return true 96 | 97 | }).sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date)) 98 | } 99 | }, 100 | 101 | totalPages() { 102 | 103 | return Math.ceil(this.filteredList.length / this.pageSize) 104 | }, 105 | }, 106 | 107 | mounted() { 108 | this.currentPage = Math.min(Math.max(this.currentPage, 0), this.totalPages - 1) 109 | }, 110 | 111 | methods: { 112 | nextPage() { 113 | this.currentPage = this.currentPage >= this.totalPages - 1 ? this.totalPages - 1 : this.currentPage + 1 114 | }, 115 | previousPage() { 116 | this.currentPage = this.currentPage < 0 ? 0 : this.currentPage - 1 117 | }, 118 | addTag(tag) { 119 | const tagExists = this.selectedTags.some(item => { 120 | return item === tag 121 | }) 122 | 123 | if (!tagExists){ 124 | this.selectedTags = this.selectedTags.concat(tag) 125 | } 126 | }, 127 | removeTag(tag) { 128 | this.selectedTags.filter(t => t != tag) 129 | }, 130 | resetTags(){ 131 | this.selectedTags = [] 132 | } 133 | } 134 | } 135 | </script> 136 | 137 | <style lang="stylus" scoped> 138 | .blog-list 139 | padding 0 140 | margin 0 141 | 142 | .blog-list__item 143 | list-style-type none 144 | 145 | .blog-list__tags 146 | margin-bottom 15px 147 | 148 | .button--pagination 149 | text-decoration none 150 | color lighten($textColor, 25%) 151 | &:hover 152 | text-decoration none !important 153 | border-bottom 2px solid $accentColor 154 | 155 | .clear-filter-btn 156 | align-self center 157 | margin-left 20px 158 | 159 | .filtered-heading 160 | display flex 161 | 162 | .pagination 163 | display flex 164 | justify-content space-between 165 | </style> 166 | -------------------------------------------------------------------------------- /src/.vuepress/theme/components/Post.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="theme-default-content"> 3 | <article> 4 | <header class="header"> 5 | <section> 6 | <TagList :tags="$frontmatter.tags" /> 7 | </section> 8 | <h1 class="title">{{ $page.title }}</h1> 9 | <template v-if="$page.frontmatter.excerpt"> 10 | <p class="excerpt">{{ $page.frontmatter.excerpt }}</p> 11 | </template> 12 | <section> 13 | <PostMeta :post="$page" show-updated/> 14 | </section> 15 | </header> 16 | 17 | <section> 18 | <Content class="body" :custom="false"/> 19 | </section> 20 | 21 | <div class="page-edit"> 22 | <div 23 | class="edit-link" 24 | v-if="editLink" 25 | > 26 | <a 27 | :href="editLink" 28 | target="_blank" 29 | rel="noopener noreferrer" 30 | >{{ editLinkText }}</a> 31 | <OutboundLink/> 32 | </div> 33 | </div> 34 | 35 | <div class="page-nav" v-if="prev || next"> 36 | <p class="inner"> 37 | <span 38 | v-if="prev" 39 | class="prev" 40 | > 41 | ← 42 | <router-link 43 | v-if="prev" 44 | class="prev" 45 | :to="prev.path" 46 | > 47 | {{ prev.title || prev.path }} 48 | </router-link> 49 | </span> 50 | 51 | <span 52 | v-if="next" 53 | class="next" 54 | > 55 | <router-link 56 | v-if="next" 57 | :to="next.path" 58 | > 59 | {{ next.title || next.path }} 60 | </router-link> 61 | → 62 | </span> 63 | </p> 64 | </div> 65 | 66 | <slot name="bottom"/> 67 | </article> 68 | </div> 69 | </template> 70 | 71 | <script> 72 | import moment from 'moment' 73 | import TagList from './TagList' 74 | import PostMeta from './PostMeta' 75 | import { resolvePage, normalize, outboundRE, endingSlashRE } from '../util' 76 | 77 | export default { 78 | components: { TagList, PostMeta }, 79 | props: ['sidebarItems'], 80 | 81 | computed: { 82 | prev () { 83 | const prev = this.$page.frontmatter.prev 84 | if (prev === false) { 85 | return 86 | } else if (prev) { 87 | return resolvePage(this.$site.pages, prev, this.$route.path) 88 | } else { 89 | return resolvePrev(this.$page, this.sidebarItems) 90 | } 91 | }, 92 | 93 | next () { 94 | const next = this.$page.frontmatter.next 95 | if (next === false) { 96 | return 97 | } else if (next) { 98 | return resolvePage(this.$site.pages, next, this.$route.path) 99 | } else { 100 | return resolveNext(this.$page, this.sidebarItems) 101 | } 102 | }, 103 | 104 | editLink () { 105 | if (this.$page.frontmatter.editLink === false) { 106 | return 107 | } 108 | const { 109 | repo, 110 | editLinks, 111 | docsDir = '', 112 | docsBranch = 'master', 113 | docsRepo = repo 114 | } = this.$site.themeConfig 115 | 116 | let path = normalize(this.$page.path) 117 | if (endingSlashRE.test(path)) { 118 | path += 'README.md' 119 | } else { 120 | path += '.md' 121 | } 122 | if (docsRepo && editLinks) { 123 | return this.createEditLink(repo, docsRepo, docsDir, docsBranch, path) 124 | } 125 | }, 126 | 127 | editLinkText () { 128 | return ( 129 | this.$themeLocaleConfig.editLinkText || 130 | this.$site.themeConfig.editLinkText || 131 | `Edit this page` 132 | ) 133 | }, 134 | }, 135 | 136 | methods: { 137 | createEditLink (repo, docsRepo, docsDir, docsBranch, path) { 138 | const bitbucket = /bitbucket.org/ 139 | if (bitbucket.test(repo)) { 140 | const base = outboundRE.test(docsRepo) 141 | ? docsRepo 142 | : repo 143 | return ( 144 | base.replace(endingSlashRE, '') + 145 | `/${docsBranch}` + 146 | (docsDir ? '/' + docsDir.replace(endingSlashRE, '') : '') + 147 | path + 148 | `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default` 149 | ) 150 | } 151 | 152 | const base = outboundRE.test(docsRepo) 153 | ? docsRepo 154 | : `https://github.com/${docsRepo}` 155 | 156 | return ( 157 | base.replace(endingSlashRE, '') + 158 | `/edit/${docsBranch}` + 159 | (docsDir ? '/' + docsDir.replace(endingSlashRE, '') : '') + 160 | path 161 | ) 162 | } 163 | } 164 | } 165 | 166 | function resolvePrev (page, items) { 167 | return find(page, items, -1) 168 | } 169 | 170 | function resolveNext (page, items) { 171 | return find(page, items, 1) 172 | } 173 | 174 | function find (page, items, offset) { 175 | const res = [] 176 | items.forEach(item => { 177 | if (item.type === 'group') { 178 | res.push(...item.children || []) 179 | } else { 180 | res.push(item) 181 | } 182 | }) 183 | for (let i = 0; i < res.length; i++) { 184 | const cur = res[i] 185 | if (cur.type === 'page' && cur.path === page.path) { 186 | return res[i + offset] 187 | } 188 | } 189 | } 190 | </script> 191 | 192 | <style lang="stylus"> 193 | .body 194 | margin-bottom 3rem 195 | </style> 196 | 197 | <style lang="stylus" scoped> 198 | 199 | .header 200 | padding-bottom 1.5rem 201 | margin-bottom 1.5rem 202 | 203 | .title 204 | font-size 3.2rem 205 | margin 0 0 .4em 206 | margin-bottom .5rem 207 | 208 | .excerpt 209 | font-size 1.2rem 210 | color lighten($textColor, 25%) 211 | margin 0 0 .5em 212 | line-height 1.4em 213 | margin-bottom 1rem 214 | 215 | .page-edit 216 | padding-top 1rem 217 | padding-bottom 1rem 218 | padding-left 0 219 | padding-right 0 220 | overflow auto 221 | .edit-link 222 | display inline-block 223 | a 224 | color lighten($textColor, 25%) 225 | margin-right .25rem 226 | 227 | @media (max-width: $MQMobile) 228 | .page-edit 229 | .edit-link 230 | margin-bottom .5rem 231 | .last-updated 232 | font-size .8em 233 | float none 234 | text-align left 235 | 236 | @media (max-width: $MQMobileNarrow) { 237 | .title { 238 | font-size: 2.441rem; 239 | } 240 | } 241 | </style> 242 | -------------------------------------------------------------------------------- /src/.vuepress/theme/components/PostMeta.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <span class="meta"> 3 | <time datetime="$page.lastUpdated"></time> {{ content() }} 4 | </span> 5 | </template> 6 | 7 | <script> 8 | import { formatPublishDate } from '../util' 9 | import moment from 'moment' 10 | 11 | export default { 12 | props: { 13 | post: Object, 14 | showUpdated: { 15 | type: Boolean, 16 | default: false 17 | } 18 | }, 19 | methods: { 20 | formatPublishDate, 21 | content() { 22 | const content = [ 23 | this.publishedAt(), 24 | this.post.readingTime && this.post.readingTime.text, 25 | this.showUpdated && this.lastUpdated() 26 | ] 27 | return content.filter(x => x).join(' • ') 28 | }, 29 | shouldShowSeparator () { 30 | return this.post.frontmatter.date && this.post.readingTime 31 | }, 32 | publishedAt () { 33 | return this.post.frontmatter.date && moment(this.post.frontmatter.date).format('DD MMM YYYY') 34 | }, 35 | lastUpdated () { 36 | if (this.$page.lastUpdated) { 37 | return this.lastUpdatedText() + ' ' + moment(this.$page.lastUpdated).fromNow() 38 | } 39 | }, 40 | lastUpdatedText () { 41 | if (typeof this.$themeLocaleConfig.lastUpdated === 'string') { 42 | return this.$themeLocaleConfig.lastUpdated 43 | } 44 | if (typeof this.$site.themeConfig.lastUpdated === 'string') { 45 | return this.$site.themeConfig.lastUpdated 46 | } 47 | return 'Last Updated' 48 | }, 49 | } 50 | } 51 | </script> 52 | 53 | <style lang="stylus" scoped> 54 | .meta 55 | color #aaa 56 | font-size .9rem 57 | </style> -------------------------------------------------------------------------------- /src/.vuepress/theme/components/PostPreview.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <article class="post-preview"> 3 | <TagList :tags="item.frontmatter.tags" v-on:tag-click="$emit('add-tag', $event)" /> 4 | <router-link class="link" :to="item.path"> 5 | <header class="header"> 6 | <h2 class="title">{{ item.frontmatter.title }}</h2> 7 | </header> 8 | <section v-if="item.frontmatter.excerpt" class="excerpt"> 9 | <p>{{ item.frontmatter.excerpt }}</p> 10 | </section> 11 | </router-link> 12 | <footer> 13 | <PostMeta :post="item" /> 14 | </footer> 15 | </article> 16 | </template> 17 | 18 | <script> 19 | import TagList from './TagList' 20 | import PostMeta from './PostMeta' 21 | 22 | export default { 23 | name: 'PostPreview', 24 | components: { TagList, PostMeta }, 25 | props: { 26 | item: { 27 | type: Object, 28 | required: true 29 | } 30 | } 31 | } 32 | </script> 33 | 34 | <style lang="stylus" scoped> 35 | .post-preview 36 | margin 60px 0 37 | 38 | .link, .link:hover 39 | display block 40 | text-decoration none !important 41 | color unset 42 | 43 | .title 44 | font-size 2.2rem 45 | margin 0 0 0.4em 46 | padding-bottom unset 47 | 48 | .excerpt 49 | font-weight 400 50 | color lighten($textColor, 25%) 51 | </style> 52 | -------------------------------------------------------------------------------- /src/.vuepress/theme/components/TagList.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <ul class="list"> 3 | <li class="list-item" v-for="tag in tags"> 4 | <a href="#" @click="$emit('tag-click', tag)"> 5 | <span class="tag tag-accent">{{ tag }}</span> 6 | </a> 7 | </li> 8 | </ul> 9 | </template> 10 | 11 | <script> 12 | export default { 13 | props: ['tags'] 14 | } 15 | </script> 16 | 17 | <style lang="stylus" scoped> 18 | .list 19 | display flex 20 | list-style none 21 | margin 0 22 | padding 0 23 | 24 | .list-item 25 | margin-right 4px 26 | 27 | .tag 28 | display inline-block 29 | margin-right 0.8em 30 | font-size .75rem 31 | font-weight 700 32 | line-height 1 33 | text-align center 34 | white-space nowrap 35 | vertical-align baseline 36 | border-radius .25rem 37 | 38 | .tag-accent 39 | color #42b983 40 | </style> -------------------------------------------------------------------------------- /src/.vuepress/theme/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extend: '@vuepress/theme-default' 3 | } -------------------------------------------------------------------------------- /src/.vuepress/theme/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div 3 | class="theme-container" 4 | :class="pageClasses" 5 | @touchstart="onTouchStart" 6 | @touchend="onTouchEnd" 7 | > 8 | <Navbar 9 | v-if="shouldShowNavbar" 10 | @toggle-sidebar="toggleSidebar" 11 | /> 12 | 13 | <div 14 | class="sidebar-mask" 15 | @click="toggleSidebar(false)" 16 | ></div> 17 | 18 | <Sidebar 19 | :items="sidebarItems" 20 | @toggle-sidebar="toggleSidebar" 21 | > 22 | <template #top> 23 | <slot name="sidebar-top" /> 24 | </template> 25 | <template #bottom> 26 | <slot name="sidebar-bottom"/> 27 | </template> 28 | </Sidebar> 29 | 30 | <Home v-if="$page.frontmatter.home"/> 31 | 32 | <Blog 33 | v-else-if="$page.frontmatter.blogList" 34 | :pages="$site.pages" 35 | :page-size="$site.themeConfig.pageSize" 36 | :start-page="$site.themeConfig.startPage" 37 | /> 38 | 39 | <ArchiveList 40 | v-else-if="$page.frontmatter.archive" 41 | :pages="$site.pages" 42 | /> 43 | 44 | <Post 45 | v-else-if="$page.frontmatter.blog" 46 | :sidebar-items="sidebarItems" 47 | /> 48 | 49 | <Page 50 | v-else 51 | :sidebar-items="sidebarItems" 52 | > 53 | <template #top> 54 | <slot name="page-top"/> 55 | </template> 56 | <template #bottom> 57 | <slot name="page-bottom"/> 58 | </template> 59 | </Page> 60 | </div> 61 | </template> 62 | 63 | <script> 64 | import Home from '@parent-theme/components/Home.vue' 65 | import Navbar from '@parent-theme/components/Navbar.vue' 66 | import Page from '@parent-theme/components/Page.vue' 67 | import Sidebar from '@parent-theme/components/Sidebar.vue' 68 | import Post from '../components/Post.vue' 69 | import Blog from '../components/Blog.vue' 70 | import ArchiveList from '../components/ArchiveList.vue' 71 | import { resolveSidebarItems } from '@parent-theme/util' 72 | 73 | export default { 74 | components: { 75 | Home, 76 | Page, 77 | Blog, 78 | ArchiveList, 79 | Post, 80 | Sidebar, 81 | Navbar 82 | }, 83 | 84 | data () { 85 | return { 86 | isSidebarOpen: false 87 | } 88 | }, 89 | 90 | computed: { 91 | shouldShowNavbar () { 92 | const { themeConfig } = this.$site 93 | const { frontmatter } = this.$page 94 | if ( 95 | frontmatter.navbar === false 96 | || themeConfig.navbar === false) { 97 | return false 98 | } 99 | return ( 100 | this.$title 101 | || themeConfig.logo 102 | || themeConfig.repo 103 | || themeConfig.nav 104 | || this.$themeLocaleConfig.nav 105 | ) 106 | }, 107 | 108 | shouldShowSidebar () { 109 | const { frontmatter } = this.$page 110 | return ( 111 | !frontmatter.home 112 | && frontmatter.sidebar !== false 113 | && this.sidebarItems.length 114 | ) 115 | }, 116 | 117 | sidebarItems () { 118 | return resolveSidebarItems( 119 | this.$page, 120 | this.$page.regularPath, 121 | this.$site, 122 | this.$localePath 123 | ) 124 | }, 125 | 126 | pageClasses () { 127 | const userPageClass = this.$page.frontmatter.pageClass 128 | return [ 129 | { 130 | 'no-navbar': !this.shouldShowNavbar, 131 | 'sidebar-open': this.isSidebarOpen, 132 | 'no-sidebar': !this.shouldShowSidebar 133 | }, 134 | userPageClass 135 | ] 136 | } 137 | }, 138 | 139 | mounted () { 140 | this.$router.afterEach(() => { 141 | this.isSidebarOpen = false 142 | }) 143 | }, 144 | 145 | methods: { 146 | toggleSidebar (to) { 147 | this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen 148 | this.$emit('toggle-sidebar', this.isSidebarOpen) 149 | }, 150 | 151 | // side swipe 152 | onTouchStart (e) { 153 | this.touchStart = { 154 | x: e.changedTouches[0].clientX, 155 | y: e.changedTouches[0].clientY 156 | } 157 | }, 158 | 159 | onTouchEnd (e) { 160 | const dx = e.changedTouches[0].clientX - this.touchStart.x 161 | const dy = e.changedTouches[0].clientY - this.touchStart.y 162 | if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) { 163 | if (dx > 0 && this.touchStart.x <= 80) { 164 | this.toggleSidebar(true) 165 | } else { 166 | this.toggleSidebar(false) 167 | } 168 | } 169 | } 170 | } 171 | } 172 | </script> 173 | -------------------------------------------------------------------------------- /src/.vuepress/theme/util.js: -------------------------------------------------------------------------------- 1 | export const hashRE = /#.*$/ 2 | export const extRE = /\.(md|html)$/ 3 | export const endingSlashRE = /\/$/ 4 | export const outboundRE = /^(https?:|mailto:|tel:)/ 5 | export const xmlRE = /(\.xml)/ 6 | 7 | export function normalize(path) { 8 | return decodeURI(path) 9 | .replace(hashRE, '') 10 | .replace(extRE, '') 11 | } 12 | 13 | export function getHash(path) { 14 | const match = path.match(hashRE) 15 | if (match) { 16 | return match[0] 17 | } 18 | } 19 | 20 | export function isExternal(path) { 21 | return outboundRE.test(path) 22 | } 23 | 24 | export function isXml(path) { 25 | return xmlRE.test(path) 26 | } 27 | 28 | export function isMailto(path) { 29 | return /^mailto:/.test(path) 30 | } 31 | 32 | export function isTel(path) { 33 | return /^tel:/.test(path) 34 | } 35 | 36 | export function ensureExt(path) { 37 | if (isExternal(path)) { 38 | return path 39 | } 40 | const hashMatch = path.match(hashRE) 41 | const hash = hashMatch ? hashMatch[0] : '' 42 | const normalized = normalize(path) 43 | 44 | if (endingSlashRE.test(normalized) || isXml(normalized)) { 45 | return path 46 | } else { 47 | return normalized + '.html' + hash 48 | } 49 | } 50 | 51 | export function isActive(route, path) { 52 | const routeHash = route.hash 53 | const linkHash = getHash(path) 54 | if (linkHash && routeHash !== linkHash) { 55 | return false 56 | } 57 | const routePath = normalize(route.path) 58 | const pagePath = normalize(path) 59 | return routePath === pagePath 60 | } 61 | 62 | export function resolvePage(pages, rawPath, base) { 63 | if (base) { 64 | rawPath = resolvePath(rawPath, base) 65 | } 66 | const path = normalize(rawPath) 67 | for (let i = 0; i < pages.length; i++) { 68 | if (normalize(pages[i].path) === path) { 69 | return Object.assign({}, pages[i], { 70 | type: 'page', 71 | path: ensureExt(rawPath) 72 | }) 73 | } 74 | } 75 | console.error( 76 | `[vuepress] No matching page found for sidebar item "${rawPath}"` 77 | ) 78 | return {} 79 | } 80 | 81 | function resolvePath(relative, base, append) { 82 | const firstChar = relative.charAt(0) 83 | if (firstChar === '/') { 84 | return relative 85 | } 86 | 87 | if (firstChar === '?' || firstChar === '#') { 88 | return base + relative 89 | } 90 | 91 | const stack = base.split('/') 92 | 93 | // remove trailing segment if: 94 | // - not appending 95 | // - appending to trailing slash (last segment is empty) 96 | if (!append || !stack[stack.length - 1]) { 97 | stack.pop() 98 | } 99 | 100 | // resolve relative path 101 | const segments = relative.replace(/^\//, '').split('/') 102 | for (let i = 0; i < segments.length; i++) { 103 | const segment = segments[i] 104 | if (segment === '..') { 105 | stack.pop() 106 | } else if (segment !== '.') { 107 | stack.push(segment) 108 | } 109 | } 110 | 111 | // ensure leading slash 112 | if (stack[0] !== '') { 113 | stack.unshift('') 114 | } 115 | 116 | return stack.join('/') 117 | } 118 | 119 | export function resolveSidebarItems(page, route, site, localePath) { 120 | const { pages, themeConfig } = site 121 | 122 | const localeConfig = 123 | localePath && themeConfig.locales 124 | ? themeConfig.locales[localePath] || themeConfig 125 | : themeConfig 126 | 127 | const pageSidebarConfig = 128 | page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar 129 | if (pageSidebarConfig === 'auto') { 130 | return resolveHeaders(page) 131 | } 132 | 133 | const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar 134 | if (!sidebarConfig) { 135 | return [] 136 | } else { 137 | const { base, config } = resolveMatchingConfig(route, sidebarConfig) 138 | return config ? config.map(item => resolveItem(item, pages, base)) : [] 139 | } 140 | } 141 | 142 | function resolveHeaders(page) { 143 | const headers = groupHeaders(page.headers || []) 144 | return [ 145 | { 146 | type: 'group', 147 | collapsable: false, 148 | title: page.title, 149 | children: headers.map(h => ({ 150 | type: 'auto', 151 | title: h.title, 152 | basePath: page.path, 153 | path: page.path + '#' + h.slug, 154 | children: h.children || [] 155 | })) 156 | } 157 | ] 158 | } 159 | 160 | export function groupHeaders(headers) { 161 | // group h3s under h2 162 | headers = headers.map(h => Object.assign({}, h)) 163 | let lastH2 164 | headers.forEach(h => { 165 | if (h.level === 2) { 166 | lastH2 = h 167 | } else if (lastH2) { 168 | ;(lastH2.children || (lastH2.children = [])).push(h) 169 | } 170 | }) 171 | return headers.filter(h => h.level === 2) 172 | } 173 | 174 | export function resolveNavLinkItem(linkItem) { 175 | return Object.assign(linkItem, { 176 | type: linkItem.items && linkItem.items.length ? 'links' : 'link' 177 | }) 178 | } 179 | 180 | export function resolveMatchingConfig(route, config) { 181 | if (Array.isArray(config)) { 182 | return { 183 | base: '/', 184 | config: config 185 | } 186 | } 187 | for (const base in config) { 188 | if (ensureEndingSlash(route.path).indexOf(base) === 0) { 189 | return { 190 | base, 191 | config: config[base] 192 | } 193 | } 194 | } 195 | return {} 196 | } 197 | 198 | export function formatPublishDate(date) { 199 | const dateFormat = new Date(date) 200 | const options = { 201 | year: 'numeric', 202 | month: 'short', 203 | day: 'numeric' 204 | } 205 | 206 | return dateFormat.toLocaleDateString('en-US', options) 207 | } 208 | 209 | function ensureEndingSlash(path) { 210 | return /(\.html|\/)$/.test(path) ? path : path + '/' 211 | } 212 | 213 | function resolveItem(item, pages, base, isNested) { 214 | if (typeof item === 'string') { 215 | return resolvePage(pages, item, base) 216 | } else if (Array.isArray(item)) { 217 | return Object.assign(resolvePage(pages, item[0], base), { 218 | title: item[1] 219 | }) 220 | } else { 221 | if (isNested) { 222 | console.error( 223 | '[vuepress] Nested sidebar groups are not supported. ' + 224 | 'Consider using navbar + categories instead.' 225 | ) 226 | } 227 | const children = item.children || [] 228 | return { 229 | type: 'group', 230 | title: item.title, 231 | children: children.map(child => resolveItem(child, pages, base, true)), 232 | collapsable: item.collapsable !== false 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | home: true 4 | heroText: Hero 5 | tagline: Some tagline 6 | actionText: Go To Github 7 | actionLink: https://github.com/bencodezen/vuepress-blog-boilerplate 8 | features: 9 | - 10 | title: Feature 1 11 | details: Feature Details 12 | - 13 | title: Feature 2 14 | details: Feature Details 15 | - 16 | title: Feature 3 17 | details: Feature Details 18 | footer: This is a footer 19 | --- -------------------------------------------------------------------------------- /src/archive/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Archive 3 | archive: true 4 | --- 5 | -------------------------------------------------------------------------------- /src/blog/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Blog 3 | blogList: true 4 | --- -------------------------------------------------------------------------------- /src/blog/my-first-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My First Post 3 | date: 2018-12-23 20:47:18 4 | excerpt: Here is a little bit about my post. 5 | type: post 6 | blog: true 7 | tags: 8 | - HTML 9 | - CSS 10 | - JavaScript 11 | --- 12 | 13 | ## Hello Dr. Zoidberg 14 | 15 | Dr. Zoidberg, that doesn't make sense. But, okay! No! The kind with looting and maybe starting a few fires! Well, then good news! It's a suppository. Hey, guess what you're accessories to. Hey, guess what you're accessories to. 16 | -------------------------------------------------------------------------------- /src/blog/my-fourth-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My Fourth Post 3 | date: 2018-12-26 21:50:21 4 | type: post 5 | blog: true 6 | excerpt: Hello 7 | tags: 8 | - HTML 9 | --- 10 | -------------------------------------------------------------------------------- /src/blog/my-second-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: This is My Future Post for 2088 3 | date: 2088-12-27T02:39:48.644Z 4 | excerpt: This post won't appear on the blog list till 2088! 5 | type: post 6 | blog: true 7 | --- 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/blog/my-third-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My Third Post 3 | date: 2018-12-26 21:50:21 4 | type: post 5 | blog: true 6 | excerpt: Hello 7 | tags: 8 | - JavaScript 9 | --- 10 | --------------------------------------------------------------------------------