├── .github └── FUNDING.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── README.md ├── content │ ├── en │ │ ├── concepts │ │ │ ├── cli.md │ │ │ ├── components.md │ │ │ ├── content.md │ │ │ ├── pages.md │ │ │ └── vuex-store.md │ │ ├── index.md │ │ └── manual-installation.md │ └── settings.json ├── nuxt.config.js ├── package.json ├── static │ ├── icon.png │ ├── logo-dark.svg │ ├── logo-light.svg │ └── preview.png ├── tailwind.config.js └── yarn.lock ├── package.json ├── src ├── _templates │ ├── author │ │ └── new │ │ │ ├── author.ejs.t │ │ │ └── prompt.js │ ├── category │ │ └── new │ │ │ ├── category.ejs.t │ │ │ └── prompt.js │ ├── post │ │ └── new │ │ │ ├── post.ejs.t │ │ │ └── prompt.js │ └── slugify.js ├── components │ ├── app │ │ └── AppHeader.vue │ ├── article │ │ └── ArticleListItem.vue │ ├── author │ │ └── AuthorAvatar.vue │ └── post │ │ ├── Post.vue │ │ └── PostList.vue ├── index.d.ts ├── index.js ├── layouts │ └── default.vue ├── plugins │ ├── init.js │ └── update.client.js ├── store │ └── index.js └── tailwind.config.js └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: beliolfa -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64robots/nuxt-content-blog/2ad6eaa0be39d60bd7bc17ca5ad1860626472533/CHANGELOG.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 64 Robots with the contributions of: 4 | - Javier Martinez (64 Robots Company) 5 | - Pepe García (64 Robots Company) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @64robots/nuxt-content-blog 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | [![License][license-src]][license-href] 6 | 7 | > Create your own @nuxt/content blog in seconds! 8 | 9 | ## Getting Started 10 | 11 | ```bash 12 | # Yarn 13 | yarn create nuxt-content-blog 14 | 15 | # NPX 16 | npx create-nuxt-content-blog 17 | ``` 18 | 19 | It's as simple as that! 20 | 21 | [📚 Documentation](https://nuxt-content-blog.netlify.app) 22 | 23 | ## License 24 | 25 | [MIT License](./LICENSE) 26 | 27 | 28 | [npm-version-src]: https://img.shields.io/npm/v/@64robots/nuxt-content-blog/latest.svg 29 | [npm-version-href]: https://npmjs.com/package/@64robots/nuxt-content-blog 30 | 31 | [npm-downloads-src]: https://img.shields.io/npm/dt/@64robots/nuxt-content-blog.svg 32 | [npm-downloads-href]: https://npmjs.com/package/@64robots/nuxt-content-blog 33 | 34 | [license-src]: https://img.shields.io/npm/l/@64robots/nuxt-content-blog.svg 35 | [license-href]: https://npmjs.com/package/@64robots/nuxt-content-blog 36 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # docs 2 | 3 | ## Setup 4 | 5 | Install dependencies: 6 | 7 | ```bash 8 | yarn install 9 | ``` 10 | 11 | ## Development 12 | 13 | ```bash 14 | yarn dev 15 | ``` 16 | 17 | ## Static Generation 18 | 19 | This will create the `dist/` directory for publishing to static hosting: 20 | 21 | ```bash 22 | yarn generate 23 | ``` 24 | 25 | To preview the static generated app, run `yarn start` 26 | 27 | For detailed explanation on how things work, checkout [nuxt/content](https://content.nuxtjs.org) and [@nuxt/content theme docs](https://content.nuxtjs.org/themes-docs). 28 | -------------------------------------------------------------------------------- /docs/content/en/concepts/cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Command-line tools' 3 | description: 'Create your own @nuxt/content blog in seconds!' 4 | position: 5 5 | category: 'Configuration' 6 | --- 7 | You can use these convenient helpers for creating Authors, Categories and Posts: 8 | 9 | - `yarn new:author` 10 | - `yarn new:category` 11 | - `yarn new:post` 12 | ![Creating a Post](https://user-images.githubusercontent.com/12644599/93211098-0b32fa00-f761-11ea-9e33-ebe617dc1ae9.gif) 13 | These commands will generate the files in the right folders -------------------------------------------------------------------------------- /docs/content/en/concepts/components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Components' 3 | description: 'Create your own @nuxt/content blog in seconds!' 4 | position: 4 5 | category: 'Configuration' 6 | --- 7 | The package comes with some global Vue.js components ready out of the box. 8 | 9 | ### PostList 10 | 11 | The `PostList` component is used to render a collection of posts. By default it renders the `posts` collection but you can specify your own by the `collection`prop. 12 | 13 | ```vue 14 | 15 | 16 | 17 | ``` 18 | You can also render a single category. Note that you also easily can handle errors or perform redirects by using the `redirect` prop. 19 | You will use the posts slot containing all the matched posts. 20 | 21 | ```vue 22 | 23 |
24 | {{ error.message }} 25 |
26 |
27 |
{{ $route.params.slug }}
28 |
29 | 30 |
31 |
32 |
33 | ``` 34 | 35 | | prop | type | default | | | 36 | |------------|--------|---------|---|---| 37 | | category | String | null | | | 38 | | collection | String | posts | | | 39 | | redirect | String | null | | | 40 | 41 | ### Post 42 | 43 | The `Post` component render a given post by using the `permalink` prop. Note that it also has the `redirect` and `error` prop. 44 | The `post` and `author` slots are available to render the matched content 45 | 46 | ```vue 47 | 48 | 49 | 50 | ``` 51 | 52 | | prop | type | default | | | 53 | |------------|--------|---------|---|---| 54 | | permalink | String | '' | | | 55 | | redirect | String | null | | | 56 | -------------------------------------------------------------------------------- /docs/content/en/concepts/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Content' 3 | description: 'Create your own @nuxt/content blog in seconds!' 4 | position: 3 5 | category: 'Configuration' 6 | --- 7 | 8 | Once you've setup your blog, you can directly start writing your content. Here is a few things you should know before put your hands down to work. 9 | 10 | This is how a the `/content` folder is structured 11 | 12 | ```bash 13 | content/ 14 | categories/ 15 | frontend.json 16 | authors/ 17 | sample-author.json 18 | posts/ 19 | my-awesome-blog.md 20 | ``` 21 | 22 | ### Categories 23 | 24 | This is where you put your categories, just create a `.json` file with the following content in it. 25 | 26 | You can create them manually or using the command line tool `yarn new:category` 27 | 28 | **Example** 29 | 30 | ```json[frontend.json] 31 | { 32 | "name": "Frontend" 33 | } 34 | ``` 35 | 36 | ### Authors 37 | 38 | This is where you put your authors, just create a `.json` file with the following content in it. 39 | 40 | You can create them manually or using the command line tool `yarn new:author` 41 | 42 | **Example** 43 | 44 | ```json[sample-author.json] 45 | { 46 | "name": "Sample Author", 47 | "avatar": "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80", 48 | "twitter": "@example" 49 | } 50 | ``` 51 | 52 | ### Posts 53 | 54 | This is where you put your blog posts, see [writing content](https://content.nuxtjs.org/writing) in `@nuxt/content` for reference 55 | 56 | You can create them manually or using the command line tool `yarn new:post` 57 | 58 | ### Front-matter 59 | 60 | To make it work properly, make sure to include these properties in the front-matter section of your markdown files. 61 | 62 | - `title` (`String`) 63 | - The title of the blog post. Will be injected in metas 64 | - `description` (`String`) 65 | - The description of the blog post. Will be injected in metas 66 | - `image` (`String`) 67 | - This will be used a a post image 68 | - `author` (`String`) 69 | - This should match the author file name 70 | - `category` (`String`) 71 | - This should match the category slug 72 | 73 | ### Example 74 | ```bash[content/posts/frontend/first-blog-post.md] 75 | --- 76 | title: This is my first Post 77 | description: 'This is my awesome description of my first blog post' 78 | image: https://images.unsplash.com/photo-1521185496955-15097b20c5fe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1547&q=80 79 | author: sample-author 80 | category: frontend 81 | --- 82 | 83 | This is my first blog post 84 | ``` 85 | -------------------------------------------------------------------------------- /docs/content/en/concepts/pages.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Blog pages' 3 | description: 'Create your own @nuxt/content blog in seconds!' 4 | position: 6 5 | category: 'Configuration' 6 | --- 7 | There are three pages available, while you can extend them to build anything as you want around the blog, these three pages provide the minimun functionality 8 | 9 | ## Index 10 | This is the entry point of your project, normally used to show a list of your blog post. 11 | 12 | #### Example 13 | ```vue[pages/index.vue] 14 | 21 | ``` 22 | 23 | ## Permalink 24 | This page will be used to render a single blog post. The permalink prop should match the post slug you want to render, note that this is the file name. 25 | 26 | ```vue[pages/_permalink.vue] 27 | 37 | ``` 38 | ## Category slug 39 | This page will be used to render a category list. The category prop should match the category slug you want to render, note that this is the file name. 40 | 41 | ```vue[pages/category/_slug.vue] 42 | 55 | ``` -------------------------------------------------------------------------------- /docs/content/en/concepts/vuex-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Vuex Store' 3 | description: 'Create your own @nuxt/content blog in seconds!' 4 | position: 7 5 | category: 'Configuration' 6 | --- 7 | A `blog` module is avaliable to access the authors and categories with a set of actions and mutations. 8 | 9 | ## Actions 10 | 11 | - `blog/setAuthors` fetch authors and save them to the `authors` state 12 | - `blog/setCategories` fetch categories and save them to the `categories` state 13 | 14 | ## Mutations 15 | 16 | - `blog/SET_AUTHORS` save the given authors to the `authors` state 17 | - `blog/SET_CATEGOIRES` save the given categories to the `categories` state -------------------------------------------------------------------------------- /docs/content/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Quick start' 3 | description: 'Create your own @nuxt/content blog in seconds!' 4 | position: 1 5 | category: 'Getting started' 6 | --- 7 | 8 | To get started quickly you can use the [create-nuxt-content-blog](https://github.com/64robots/create-nuxt-content-blog) package. 9 | 10 | 11 | 12 | 13 | ```bash 14 | yarn create nuxt-content-blog 15 | ``` 16 | 17 | 18 | 19 | 20 | ```bash 21 | # Make sure you have npx installed (npx is shipped by default since NPM 5.2.0) or npm v6.1 or yarn. 22 | npx create-nuxt-content-blog 23 | ``` 24 | 25 | 26 | 27 | 28 | ```bash 29 | # Starting with npm v6.1 you can do: 30 | npm init nuxt-content-blog 31 | ``` 32 | 33 | 34 | 35 | 36 | It will ask few questions about your project and install all the dependencies. 37 | 38 | ## Scaffolding 39 | This option will publish a ready to use blog layout with a predefined style, check it over here [nuxt-content-blog-demo](https://nuxt-content-blog-demo.netlify.app/) 40 | 41 | ![image](https://user-images.githubusercontent.com/9825719/93459324-d601e580-f8e1-11ea-994e-566cdddf72a3.png) 42 | 43 | If you prefer to not use the provided scaffolding check out the [Pages section](concepts/pages) 44 | 45 | The next step is to get into your project folder and launch it: 46 | 47 | 48 | 49 | 50 | ```bash 51 | cd 52 | yarn dev 53 | ``` 54 | 55 | 56 | 57 | 58 | ```bash 59 | cd 60 | npm run dev 61 | ``` 62 | 63 | 64 | 65 | 66 | The application is now running on [http://localhost:3000](http://localhost:3000). Well done! 67 | -------------------------------------------------------------------------------- /docs/content/en/manual-installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Manual Installation' 3 | description: 'Create your own @nuxt/content blog in seconds!' 4 | position: 2 5 | category: 'Getting started' 6 | --- 7 | 8 | So you want to start a blog, right? Let's go for it! This themes relies on NuxtJS, so you need: 9 | 10 | ### `package.json` 11 | 12 | > This file can be created with `npm init`. 13 | 14 | Install `nuxt` and `@nuxt/content-theme-docs`: 15 | 16 | 17 | 18 | 19 | ```bash 20 | yarn add nuxt @64robots/nuxt-content-blog 21 | ``` 22 | 23 | 24 | 25 | 26 | ```bash 27 | npm install nuxt @64robots/nuxt-content-blog 28 | ``` 29 | 30 | 31 | 32 | 33 | **Example** 34 | 35 | ```json[package.json] 36 | { 37 | "name": "blog", 38 | "version": "1.0.0", 39 | "scripts": { 40 | "dev": "nuxt", 41 | "build": "nuxt build", 42 | "start": "nuxt start", 43 | "generate": "nuxt generate" 44 | }, 45 | "dependencies": { 46 | "@64robots/nuxt-content-blog": "^0.1.12", 47 | "nuxt": "^2.14.0" 48 | } 49 | } 50 | ``` 51 | 52 | ### `nuxt.config.js` 53 | 54 | Import the theme function from `@64robots/nuxt-content-blog`: 55 | 56 | ```js[nuxt.config.js] 57 | import theme from '@64robots/nuxt-content-blog' 58 | 59 | export default theme() 60 | ``` 61 | 62 | The theme exports a function to setup the `nuxt.config.js` and allows you to add / override the default config. 63 | 64 | > Check out the documentation of [defu.arrayFn](https://github.com/nuxt-contrib/defu#array-function-merger) to see how the config is merged. 65 | 66 | 67 | ### `tailwind.config.js` 68 | 69 | You can override the existing tailwind configuration, check out the [Tailwindcss documentation](https://tailwindcss.com/docs/configuration) for that. 70 | 71 | **Example** 72 | 73 | ```js[tailwind.config.js] 74 | module.exports = { 75 | theme: { 76 | extend: { 77 | colors: { 78 | } 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | ### `content/` 85 | 86 | This is where you put your markdown content files. 87 | 88 | ### `static/` 89 | 90 | This is where you put your static assets like the logo. 91 | 92 | **Example** 93 | 94 | ```bash 95 | content/ 96 | categories/ 97 | frontend.json 98 | authors/ 99 | sample-author.json 100 | posts/ 101 | my-awesome-blog.md 102 | static/ 103 | icon.png 104 | nuxt.config.js 105 | package.json 106 | ``` -------------------------------------------------------------------------------- /docs/content/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Nuxt Content Blog", 3 | "url": "https://nuxt-content-blog.netlify.app", 4 | "logo": { 5 | "light": "/logo-light.svg", 6 | "dark": "/logo-dark.svg" 7 | }, 8 | "github": "64robots/nuxt-content-blog", 9 | "twitter": "@64robots" 10 | } -------------------------------------------------------------------------------- /docs/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import theme from '@nuxt/content-theme-docs' 2 | 3 | export default theme({}) 4 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxt", 7 | "build": "nuxt build", 8 | "start": "nuxt start", 9 | "generate": "nuxt generate" 10 | }, 11 | "dependencies": { 12 | "@nuxt/content-theme-docs": "^0.5.5", 13 | "nuxt": "^2.14.4" 14 | } 15 | } -------------------------------------------------------------------------------- /docs/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64robots/nuxt-content-blog/2ad6eaa0be39d60bd7bc17ca5ad1860626472533/docs/static/icon.png -------------------------------------------------------------------------------- /docs/static/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/static/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/static/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64robots/nuxt-content-blog/2ad6eaa0be39d60bd7bc17ca5ad1860626472533/docs/static/preview.png -------------------------------------------------------------------------------- /docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: { 3 | extend: { 4 | colors: { 5 | primary: { 6 | 100: "#fae6e6", 7 | 200: "#f3ccbf", 8 | 300: "#eba199", 9 | 400: "#dc4d4d", 10 | 500: "#db4c32", 11 | 600: "#b92800", 12 | 700: "#7b1200", 13 | 800: "#5c1400", 14 | 900: "#211817", 15 | }, 16 | }, 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@64robots/nuxt-content-blog", 3 | "version": "0.2.8", 4 | "license": "MIT", 5 | "files": [ 6 | "src" 7 | ], 8 | "main": "src/", 9 | "dependencies": { 10 | "@nuxt/content": "^1.8.1", 11 | "@nuxt/types": "^2.14.4", 12 | "@nuxtjs/tailwindcss": "^3.0.2", 13 | "@tailwindcss/typography": "^0.2.0", 14 | "date-fns": "^2.16.1", 15 | "defu": "^3.1.0", 16 | "hygen": "^5.0.3", 17 | "nuxt-i18n": "^6.13.12", 18 | "reading-time": "^1.2.0" 19 | }, 20 | "devDependencies": { 21 | "babel-eslint": "^10.0.1", 22 | "eslint": "^6.1.0", 23 | "eslint-config-prettier": "^6.10.0", 24 | "eslint-plugin-nuxt": ">=0.4.2", 25 | "eslint-plugin-prettier": "^3.1.2", 26 | "nuxt": "^2.14.4" 27 | }, 28 | "types": "./src/index.d.ts" 29 | } 30 | -------------------------------------------------------------------------------- /src/_templates/author/new/author.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: content/authors/<%= slug %>.json 3 | --- 4 | { 5 | "name": "<%= name %>", 6 | "avatar": "<%= avatar %>" 7 | } 8 | -------------------------------------------------------------------------------- /src/_templates/author/new/prompt.js: -------------------------------------------------------------------------------- 1 | const slugify = require('../../slugify') 2 | 3 | module.exports = { 4 | prompt: ({ prompter }) => { 5 | return new Promise(resolve => { 6 | prompter 7 | .prompt([ 8 | { 9 | type: 'input', 10 | name: 'name', 11 | message: 'Name:', 12 | validate(value) { 13 | if (!value.length) { 14 | return 'Name is required.' 15 | } 16 | return true 17 | }, 18 | }, 19 | { 20 | type: 'input', 21 | name: 'avatar', 22 | message: 'Avatar (optional):', 23 | }, 24 | ]) 25 | .then(({ name, avatar }) => { 26 | const slug = slugify(name) 27 | resolve({ 28 | name, 29 | slug, 30 | avatar, 31 | }) 32 | }) 33 | }) 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /src/_templates/category/new/category.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: content/categories/<%= slug %>.json 3 | --- 4 | { 5 | "name": "<%= name %>" 6 | } 7 | -------------------------------------------------------------------------------- /src/_templates/category/new/prompt.js: -------------------------------------------------------------------------------- 1 | const slugify = require('../../slugify') 2 | 3 | module.exports = { 4 | prompt: ({ prompter }) => { 5 | return new Promise(resolve => { 6 | prompter 7 | .prompt([ 8 | { 9 | type: 'input', 10 | name: 'name', 11 | message: 'Name:', 12 | validate(value) { 13 | if (!value.length) { 14 | return 'Name is required.' 15 | } 16 | return true 17 | }, 18 | }, 19 | ]) 20 | .then(({ name }) => { 21 | const slug = slugify(name) 22 | resolve({ 23 | name, 24 | slug, 25 | }) 26 | }) 27 | }) 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /src/_templates/post/new/post.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: content/posts/<%= category %>/<%= permalink %>.md 3 | --- 4 | --- 5 | title: <%= title %> 6 | description: '<%= description %>' 7 | image: <%= image %> 8 | author: <%= author %> 9 | category: <%= category %> 10 | createdAt: <%= new Date().toISOString() %> 11 | --- 12 | 13 | # This is a heading 14 | This content is autogenerated. Please, open your editor and write an awesome blog post. 15 | -------------------------------------------------------------------------------- /src/_templates/post/new/prompt.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const slugify = require('../../slugify') 3 | 4 | module.exports = { 5 | prompt: ({ prompter }) => { 6 | var fs = require('fs') 7 | const contentPath = path.resolve(__dirname, '../../../../../../../content/') 8 | const authorsPath = `${contentPath}/authors` 9 | const categoriesPath = `${contentPath}/categories` 10 | 11 | const authors = fs.readdirSync(authorsPath) 12 | .filter(file => file.includes('.json')) 13 | .map(file => ({ 14 | name: file.replace('.json', ''), 15 | message: require(`${authorsPath}/${file}`).name, 16 | })) 17 | 18 | const categories = fs.readdirSync(categoriesPath) 19 | .filter(file => file.includes('.json')) 20 | .map(file => ({ 21 | name: file.replace('.json', ''), 22 | message: require(`${categoriesPath}/${file}`).name, 23 | })) 24 | 25 | return new Promise(resolve => { 26 | prompter 27 | .prompt([ 28 | { 29 | type: 'select', 30 | name: 'category', 31 | message: 'Pick Category:', 32 | choices: categories, 33 | validate(value) { 34 | if (!value.length) { 35 | return 'Blog posts must belongs to a category.' 36 | } 37 | return true 38 | }, 39 | }, 40 | { 41 | type: 'select', 42 | name: 'author', 43 | message: 'Pick Author:', 44 | choices: authors, 45 | validate(value) { 46 | if (!value.length) { 47 | return 'Blog posts must belongs to an author.' 48 | } 49 | return true 50 | }, 51 | }, 52 | { 53 | type: 'input', 54 | name: 'title', 55 | message: 'Title:', 56 | validate(value) { 57 | if (!value.length) { 58 | return 'Blog posts must have a title.' 59 | } 60 | return true 61 | }, 62 | }, 63 | { 64 | type: 'input', 65 | name: 'description', 66 | message: 'Description (optional):', 67 | }, 68 | { 69 | type: 'input', 70 | name: 'image', 71 | message: 'Image Url (optional):', 72 | }, 73 | ]) 74 | .then(({ title, description, category, author, image }) => { 75 | const permalink = slugify(title) 76 | resolve({ 77 | title, 78 | description, 79 | permalink, 80 | category, 81 | author, 82 | image, 83 | }) 84 | }) 85 | }) 86 | }, 87 | } 88 | -------------------------------------------------------------------------------- /src/_templates/slugify.js: -------------------------------------------------------------------------------- 1 | module.exports = name => 2 | name 3 | .replace(/[^a-z0-9]+/gi, '-') 4 | .replace(/^-*|-*$/g, '') 5 | .toLowerCase() 6 | -------------------------------------------------------------------------------- /src/components/app/AppHeader.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /src/components/article/ArticleListItem.vue: -------------------------------------------------------------------------------- 1 | 54 | 99 | -------------------------------------------------------------------------------- /src/components/author/AuthorAvatar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 68 | -------------------------------------------------------------------------------- /src/components/post/Post.vue: -------------------------------------------------------------------------------- 1 | 112 | -------------------------------------------------------------------------------- /src/components/post/PostList.vue: -------------------------------------------------------------------------------- 1 | 76 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { NuxtConfig } from '@nuxt/types' 2 | export default (config: NuxtConfig) => NuxtConfig -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import defu from 'defu' 3 | 4 | import tailwindConfig from './tailwind.config' 5 | 6 | const defaultConfig = { 7 | target: 'static', 8 | 9 | ssr: true, 10 | 11 | head: { 12 | meta: [ 13 | { charset: 'utf-8' }, 14 | { name: 'viewport', content: 'width=device-width, initial-scale=1' } 15 | ], 16 | link: [ 17 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, 18 | { 19 | rel: 'stylesheet', 20 | href: 'https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&display=swap', 21 | }, 22 | ], 23 | }, 24 | 25 | generate: { 26 | async routes() { 27 | const { $content } = require('@nuxt/content') 28 | const posts = await $content('posts', { deep: true }) 29 | .only(['slug']) 30 | .fetch() 31 | 32 | const categories = await $content('categories') 33 | .only(['slug']) 34 | .fetch() 35 | 36 | return [...posts.map(p => `/${p.slug}`), ...categories.map(c => `/category/${c.slug}`)] 37 | }, 38 | }, 39 | 40 | transpile: [ 41 | __dirname 42 | ], 43 | 44 | css: [], 45 | 46 | plugins: [ 47 | '~/node_modules/@64robots/nuxt-content-blog/src/plugins/init', 48 | '~/node_modules/@64robots/nuxt-content-blog/src/plugins/update.client', 49 | ], 50 | 51 | buildModules: [ 52 | '@nuxtjs/tailwindcss', 53 | ], 54 | 55 | modules: [ 56 | '@nuxt/content' 57 | ], 58 | 59 | components: true, 60 | 61 | hooks: { 62 | 'modules:before': ({ nuxt }) => { 63 | // Configure `components/` dir 64 | nuxt.hook('components:dirs', (dirs) => { 65 | dirs.push({ 66 | path: path.resolve(__dirname, 'components'), 67 | global: true 68 | }) 69 | }) 70 | // Configure `tailwind.config.js` path 71 | nuxt.options.tailwindcss.configPath = nuxt.options.tailwindcss.configPath || path.resolve(nuxt.options.rootDir, 'tailwind.config.js') 72 | nuxt.options.tailwindcss.cssPath = nuxt.options.tailwindcss.cssPath || path.resolve(nuxt.options.rootDir, nuxt.options.dir.assets, 'css', 'tailwind.css') 73 | }, 74 | }, 75 | content: {}, 76 | 77 | tailwindcss: { 78 | config: tailwindConfig 79 | } 80 | } 81 | 82 | export default (userConfig) => { 83 | const config = defu.arrayFn(userConfig, defaultConfig) 84 | 85 | config.hooks['content:file:beforeInsert'] = document => { 86 | if (document.extension === '.md') { 87 | const { text } = require('reading-time')(document.text) 88 | document.readingTime = text 89 | 90 | if (!document.category) { 91 | document.category = document.dir.replace('/category/', '') 92 | } 93 | 94 | if (!document.permalink) { 95 | document.permalink = document.title 96 | .replace(/[^a-z0-9]+/gi, '-') 97 | .replace(/^-*|-*$/g, '') 98 | .toLowerCase() 99 | } 100 | 101 | if (!document.language) { 102 | document.language = 'en' 103 | } 104 | } 105 | } 106 | 107 | return config 108 | } 109 | -------------------------------------------------------------------------------- /src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/plugins/init.js: -------------------------------------------------------------------------------- 1 | import { actions, state, mutations } from '../store' 2 | 3 | export default async function ({ store }) { 4 | store.registerModule('blog', { 5 | namespaced: true, 6 | actions, 7 | state, 8 | mutations 9 | }) 10 | await store.dispatch('blog/setAuthors') 11 | await store.dispatch('blog/setCategories') 12 | } -------------------------------------------------------------------------------- /src/plugins/update.client.js: -------------------------------------------------------------------------------- 1 | export default function ({ store }) { 2 | // Only in development 3 | if (process.dev) { 4 | window.onNuxtReady(($nuxt) => { 5 | $nuxt.$on('content:update', ({ event, path }) => { 6 | store.dispatch('blog/setAuthors') 7 | store.dispatch('blog/setCategories') 8 | }) 9 | }) 10 | } 11 | } -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | authors: [], 3 | categories: [], 4 | }) 5 | 6 | export const actions = { 7 | async setAuthors({ commit }) { 8 | const categories = await this.$content('authors').fetch() 9 | commit('SET_AUTHORS', categories) 10 | }, 11 | 12 | async setCategories({ commit }) { 13 | const categories = await this.$content('categories').fetch() 14 | commit('SET_CATEGORIES', categories) 15 | }, 16 | } 17 | 18 | export const mutations = { 19 | SET_AUTHORS(state, authors) { 20 | state.authors = authors 21 | }, 22 | 23 | SET_CATEGORIES(state, categories) { 24 | state.categories = categories 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /src/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** TailwindCSS Configuration File 3 | ** 4 | ** Docs: https://tailwindcss.com/docs/configuration 5 | ** Default: https://github.com/tailwindcss/tailwindcss/blob/master/stubs/defaultConfig.stub.js 6 | */ 7 | 8 | const path = require("path"); 9 | 10 | module.exports = { 11 | theme: {}, 12 | variants: {}, 13 | plugins: [require("@tailwindcss/typography")], 14 | future: { 15 | removeDeprecatedGapUtilities: true, 16 | purgeLayersByDefault: true, 17 | }, 18 | purge: { 19 | // Learn more on https://tailwindcss.com/docs/controlling-file-size/#removing-unused-css 20 | enabled: process.env.NODE_ENV === "production", 21 | content: [ 22 | "content/**/*.md", 23 | path.join(__dirname, "components/**/*.vue"), 24 | path.join(__dirname, "layouts/**/*.vue"), 25 | path.join(__dirname, "pages/**/*.vue"), 26 | path.join(__dirname, "plugins/**/*.js"), 27 | "nuxt.config.js", 28 | ], 29 | }, 30 | }; --------------------------------------------------------------------------------