├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .nvmrc ├── .prettierrc ├── LICENSE ├── README.md ├── app ├── app.html ├── assets │ ├── README.md │ └── css │ │ ├── _markdown.scss │ │ ├── _mixins.scss │ │ ├── _variables.scss │ │ ├── main.scss │ │ └── tailwind.css ├── components │ ├── commons │ │ └── pagination.vue │ └── partials │ │ ├── footer.vue │ │ └── header.vue ├── config │ └── tailwind.config.js ├── content │ ├── blog │ │ ├── biryani.json │ │ ├── cat.json │ │ ├── la-croix.json │ │ ├── meditation-pop-up-forage.json │ │ ├── pug-swag.json │ │ └── test1.json │ ├── pages │ │ └── about.json │ └── settings │ │ ├── general.json │ │ └── manifest.json ├── layouts │ └── default.vue ├── pages │ ├── _page.vue │ ├── blog │ │ ├── _slug.vue │ │ ├── index.vue │ │ └── page │ │ │ └── _page.vue │ └── index.vue ├── shims │ └── index.d.ts ├── static │ ├── _redirects │ ├── admin │ │ ├── config.yml │ │ └── index.html │ └── images │ │ └── uploads │ │ ├── 81464027_471079400471586_3271125514441457664_n.jpg │ │ ├── logo.png │ │ ├── logo.svg │ │ └── pakistani-chicken-biryani.jpg ├── store │ └── index.ts ├── tailwind.config.js └── utils.ts ├── babel.config.js ├── commitlint.config.js ├── husky.config.js ├── jest.config.js ├── netlify.toml ├── nuxt.config.ts ├── package.json ├── tests ├── __mocks__ │ └── style.js └── pages │ └── page.spec.js ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: '@typescript-eslint/parser', 5 | project: './tsconfig.json', 6 | extraFileExtensions: ['.vue'], 7 | }, 8 | env: { 9 | browser: true, 10 | jest: true, 11 | node: true, 12 | }, 13 | extends: [ 14 | 'airbnb-typescript/base', 15 | 'plugin:vue-a11y/recommended', 16 | 'plugin:@typescript-eslint/recommended', 17 | 'plugin:vue/strongly-recommended', 18 | 'prettier', 19 | 'prettier/vue', 20 | 'prettier/@typescript-eslint', 21 | ], 22 | plugins: ['@typescript-eslint', 'vue-a11y', 'prettier', 'vue'], 23 | // add your custom rules here 24 | rules: { 25 | 'prettier/prettier': ['error', { singleQuote: true, trailingComma: 'es5', printWidth: 100 }], 26 | 'import/extensions': [ 27 | 'error', 28 | 'always', 29 | { 30 | js: 'never', 31 | ts: 'never', 32 | }, 33 | ], 34 | 'no-console': 0, 35 | '@typescript-eslint/no-explicit-any': 0, 36 | '@typescript-eslint/no-var-requires': 0, 37 | 'import/no-dynamic-require': 0, 38 | 'global-require': 0, 39 | 'no-underscore-dangle': 0, 40 | 'class-methods-use-this': 0, 41 | 'vue/max-attributes-per-line': 'off', 42 | 'vue/component-name-in-template-casing': [1, 'kebab-case'], 43 | }, 44 | settings: { 45 | 'import/core-modules': [ 46 | '@nuxt/config', 47 | '@nuxt/vue-app', 48 | '@nuxt/types', 49 | 'purgecss-webpack-plugin', 50 | 'vue', 51 | 'vuex', 52 | 'vue-meta', 53 | 'vue-server-renderer', 54 | 'vue-router', 55 | ], 56 | 'import/resolver': { 57 | webpack: { 58 | config: { 59 | resolve: { 60 | extensions: ['.js', '.json', '.ts', '.vue'], 61 | alias: { 62 | '~': __dirname + '/app', 63 | '@': __dirname + '/app', 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # logs 5 | npm-debug.log 6 | 7 | # Nuxt build 8 | .nuxt 9 | 10 | # Nuxt generate 11 | dist 12 | 13 | # Ignore generated service workers 14 | app/static/sw.js 15 | app/static/workbox-sw.prod* 16 | 17 | # yarn 18 | yarn-error.log 19 | 20 | # Jest Coverage 21 | coverage 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.13.1 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "printWidth": 100 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Thomas Marrec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/gomah/bluise) 2 | 3 | # Bluise - A Nuxt.js & Netlify CMS boilerplate. 4 | 5 | I wanted to explore Netlify CMS & Tailwind CSS, ended up creating this boilerplate. 6 | 7 | So far we've got: 8 | 9 | - Blog w/ posts, including pagination. 10 | - Configurable manifest & global settings. 11 | - CSS Markdown (Thanks to [https://github.com/iandinwoodie/github-markdown-tailwindcss/blob/master/markdown.css]). 12 | - Dynamic pages. 13 | - PWA ready. 14 | - SEO ready for posts & pages. 15 | - Signup form (using Netlify Forms). 16 | - Tools (Commitlint, Husky). 17 | - Typescript. 18 | 19 | Few things I'd like to add in the future: 20 | 21 | - Contact form under \_slug. 22 | - Dynamic sections/widgets. 23 | - Responsive CSS markdown. 24 | - Tests 25 | 26 | ## Quickstart 27 | 28 | ### Prerequisites 29 | 30 | - [Yarn](https://yarnpkg.com/lang/en/docs/install/#mac-tab) 31 | - [Node.js](https://nodejs.org/en/) 32 | 33 | ```bash 34 | # ensure you have the prerequisites 35 | # install 36 | brew install node && brew install yarn 37 | 38 | # OR update 39 | brew update && brew upgrade && brew install yarn 40 | 41 | # install dependencies 42 | yarn install 43 | 44 | # serve with hot reload at localhost:3000 45 | yarn dev 46 | 47 | # build for production with minification 48 | yarn generate 49 | 50 | # run all tests 51 | yarn test 52 | ``` 53 | 54 | ### Using Netlify CMS 55 | 56 | 1. Deploy to Netlify. 57 | 2. Enable Identity under Settings. 58 | 3. Configure registration preferences & external providers if needed. 59 | 4. Enable Git Gateway. 60 | 61 | _Note: You'll need to specify the Netlify URL when browsing the admin page locally._ 62 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ HEAD }} 5 | 6 | 7 | 8 | {{ APP }} 9 | 10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /app/assets/css/_markdown.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspired by 3 | * https://github.com/iandinwoodie/github-markdown-tailwindcss/blob/master/markdown.css 4 | */ 5 | 6 | .py-05 { 7 | padding-top: 0.125rem; 8 | padding-bottom: 0.125rem; 9 | } 10 | 11 | .markdown { 12 | @apply text-gray-900 leading-normal break-words; 13 | } 14 | 15 | .markdown > * + * { 16 | @apply mt-0 mb-4; 17 | } 18 | 19 | .markdown li + li { 20 | @apply mt-1; 21 | } 22 | 23 | .markdown li > p + p { 24 | @apply mt-6; 25 | } 26 | 27 | .markdown strong { 28 | @apply font-semibold; 29 | } 30 | 31 | .markdown a { 32 | @apply text-blue-600 font-semibold; 33 | } 34 | 35 | .markdown strong a { 36 | @apply font-bold; 37 | } 38 | 39 | .markdown h1 { 40 | @apply leading-tight text-5xl font-semibold mb-4 mt-6 pb-2; 41 | } 42 | 43 | .markdown h2 { 44 | @apply leading-tight text-4xl font-semibold mb-4 mt-6 pb-2; 45 | } 46 | 47 | .markdown h3 { 48 | @apply leading-snug text-2xl font-semibold mb-4 mt-6; 49 | } 50 | 51 | .markdown h4 { 52 | @apply leading-none text-lg font-semibold mb-4 mt-6; 53 | } 54 | 55 | .markdown h5 { 56 | @apply leading-tight text-base font-semibold mb-4 mt-6; 57 | } 58 | 59 | .markdown h6 { 60 | @apply leading-tight text-sm font-semibold text-gray-600 mb-4 mt-6; 61 | } 62 | 63 | .markdown blockquote { 64 | @apply text-base border-l-4 border-gray-300 pl-4 pr-4 text-gray-600; 65 | } 66 | 67 | .markdown code { 68 | @apply font-mono text-sm inline bg-gray-200 rounded px-1 py-05; 69 | } 70 | 71 | .markdown pre { 72 | @apply bg-gray-100 rounded p-4; 73 | } 74 | 75 | .markdown pre code { 76 | @apply block bg-transparent p-0 overflow-visible rounded-none; 77 | } 78 | 79 | .markdown ul { 80 | @apply text-base pl-8 list-disc; 81 | } 82 | 83 | .markdown ol { 84 | @apply text-base pl-8 list-decimal; 85 | } 86 | 87 | .markdown kbd { 88 | @apply text-xs inline-block rounded border px-1 py-05 align-middle font-normal font-mono shadow; 89 | } 90 | 91 | .markdown table { 92 | @apply text-base border-gray-600; 93 | } 94 | 95 | .markdown th { 96 | @apply border py-1 px-3; 97 | } 98 | 99 | .markdown td { 100 | @apply border py-1 px-3; 101 | } 102 | 103 | /* Override pygments style background color. */ 104 | .markdown .highlight pre { 105 | @apply bg-gray-100; 106 | } 107 | -------------------------------------------------------------------------------- /app/assets/css/_mixins.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gomah/bluise/38432163c54c1ac10bb9e43ded47de43da138cc4/app/assets/css/_mixins.scss -------------------------------------------------------------------------------- /app/assets/css/_variables.scss: -------------------------------------------------------------------------------- 1 | $bluise: #0b3765; 2 | -------------------------------------------------------------------------------- /app/assets/css/main.scss: -------------------------------------------------------------------------------- 1 | @import 'markdown'; 2 | 3 | body { 4 | @apply antialiased; 5 | @apply subpixel-antialiased; 6 | } 7 | 8 | .main { 9 | display: flex; 10 | flex-direction: column; 11 | min-height: 100vh; 12 | } 13 | 14 | .page-enter-active, 15 | .page-leave-active { 16 | transition: opacity 0.5s; 17 | } 18 | .page-enter, 19 | .page-leave-active { 20 | opacity: 0; 21 | } 22 | 23 | .slide-left-enter, 24 | .slide-right-leave-active { 25 | opacity: 0; 26 | transform: translate(30px, 0); 27 | } 28 | 29 | .slide-left-leave-active, 30 | .slide-right-enter { 31 | opacity: 0; 32 | transform: translate(-30px, 0); 33 | } 34 | -------------------------------------------------------------------------------- /app/assets/css/tailwind.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | -------------------------------------------------------------------------------- /app/components/commons/pagination.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 67 | 68 | 93 | -------------------------------------------------------------------------------- /app/components/partials/footer.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /app/components/partials/header.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 42 | 43 | 53 | -------------------------------------------------------------------------------- /app/config/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 | module.exports = { 8 | theme: {}, 9 | variants: {}, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /app/content/blog/biryani.json: -------------------------------------------------------------------------------- 1 | { 2 | "publishedAt": "28th Jan 2022", 3 | "title": "biryani", 4 | "content": "biryani chicken", 5 | "featuredImage": "/images/uploads/pakistani-chicken-biryani.jpg" 6 | } -------------------------------------------------------------------------------- /app/content/blog/cat.json: -------------------------------------------------------------------------------- 1 | { 2 | "publishedAt": "14th Aug 2020", 3 | "featuredImage": "/images/uploads/81464027_471079400471586_3271125514441457664_n.jpg", 4 | "title": "cat", 5 | "content": "A cat paint" 6 | } -------------------------------------------------------------------------------- /app/content/blog/la-croix.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "La croix", 3 | "publishedAt": "29th Nov 2019", 4 | "content": "La croix migas scenester gastropub sartorial selfies food truck ennui irony shoreditch bicycle rights. Pork belly squid meh, marfa ramps echo park taxidermy. Man bun narwhal trust fund banjo marfa tattooed copper mug woke shaman kogi hella mixtape tilde truffaut. Whatever adaptogen tbh, crucifix VHS artisan portland. Vice lomo slow-carb, truffaut sriracha snackwave enamel pin la croix small batch selvage. Fixie live-edge heirloom tousled. Celiac cray bespoke fingerstache tote bag shoreditch literally put a bird on it offal meggings williamsburg echo park pinterest try-hard.", 5 | "featuredImage": "/images/uploads/81464027_471079400471586_3271125514441457664_n.jpg" 6 | } -------------------------------------------------------------------------------- /app/content/blog/meditation-pop-up-forage.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Meditation pop-up forage", 3 | "publishedAt": "27th Nov 2019", 4 | "content": "Lorem ipsum dolor amet lo-fi vice flannel, distillery bicycle rights bitters sartorial raw denim pop-up succulents offal williamsburg iPhone gastropub. Whatever banjo hexagon shoreditch 90's disrupt next level echo park taiyaki direct trade. Williamsburg unicorn distillery kogi chartreuse health goth artisan forage cred next level authentic mixtape chambray pinterest. Umami four dollar toast hashtag kombucha, deep v iPhone chartreuse tilde normcore migas mustache skateboard hexagon vaporware. You probably haven't heard of them typewriter prism 90's, hoodie viral copper mug vegan single-origin coffee kickstarter. Vaporware intelligentsia vape austin coloring book pitchfork kinfolk chillwave woke skateboard letterpress pug aesthetic 8-bit.\n\n![](/images/uploads/logo.png)\n\nLyft authentic lumbersexual retro pork belly chambray. Meh cloud bread helvetica 8-bit. Vice meditation etsy pickled asymmetrical vinyl cliche. Occupy green juice typewriter beard.\n\nChillwave readymade heirloom cold-pressed. Hexagon occupy biodiesel dreamcatcher craft beer truffaut YOLO succulents. Blog asymmetrical direct trade cloud bread, fixie venmo forage brunch meh typewriter try-hard knausgaard freegan. Shaman single-origin coffee heirloom kitsch pop-up tilde. Etsy waistcoat slow-carb flexitarian ethical.\n\nLa croix migas scenester gastropub sartorial selfies food truck ennui irony shoreditch bicycle rights. Pork belly squid meh, marfa ramps echo park taxidermy. Man bun narwhal trust fund banjo marfa tattooed copper mug woke shaman kogi hella mixtape tilde truffaut. Whatever adaptogen tbh, crucifix VHS artisan portland. Vice lomo slow-carb, truffaut sriracha snackwave enamel pin la croix small batch selvage. Fixie live-edge heirloom tousled. Celiac cray bespoke fingerstache tote bag shoreditch literally put a bird on it offal meggings williamsburg echo park pinterest try-hard.\n\nWayfarers locavore offal tousled, semiotics quinoa gluten-free tbh tattooed. Snackwave chartreuse banh mi lumbersexual gochujang pinterest ramps man braid quinoa pickled. Godard dreamcatcher drinking vinegar cloud bread cray knausgaard mustache shabby chic pitchfork. Authentic mixtape YOLO organic shaman, put a bird on it messenger bag microdosing occupy edison bulb bushwick vexillologist jean shorts affogato ennui. Food truck meggings jianbing bitters put a bird on it.", 5 | "featuredImage": "/images/uploads/81464027_471079400471586_3271125514441457664_n.jpg" 6 | } -------------------------------------------------------------------------------- /app/content/blog/pug-swag.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Pug Swag", 3 | "publishedAt": "28th Nov 2019", 4 | "content": "Pug swag yuccie artisan hot chicken, glossier cold-pressed. Tumeric you probably haven't heard of them kinfolk yuccie. Cronut prism jean shorts, pickled kinfolk fixie freegan. Irony meh quinoa af. Kinfolk thundercats 8-bit XOXO jianbing, health goth schlitz DIY blog pok pok literally succulents echo park master cleanse sriracha." 5 | } 6 | -------------------------------------------------------------------------------- /app/content/blog/test1.json: -------------------------------------------------------------------------------- 1 | { 2 | "publishedAt": "23rd Apr 2021", 3 | "title": "test1", 4 | "content": "hello", 5 | "featuredImage": null 6 | } -------------------------------------------------------------------------------- /app/content/pages/about.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "About title", 3 | "seoDescription": "About us", 4 | "content": "hello Mustache pug irony shaman flannel yr shabby chic skateboard scenester hexagon poutine kogi XOXO etsy 90's. Live-edge shabby chic jean shorts blue bottle cornhole sustainable skateboard. Pitchfork adaptogen scenester williamsburg. Subway tile locavore trust fund yr +1. Intelligentsia chicharrones iceland, pabst ugh 8-bit sartorial readymade mlkshk. Green juice sriracha viral prism roof party hot chicken 90's fanny pack glossier ennui synth raw denim YOLO truffaut williamsburg. Banh mi tousled hell of, sartorial man bun squid fanny pack master cleanse migas occupy tote bag wolf.\n\nAsymmetrical sartorial cred offal. Kogi +1 neutra etsy normcore kitsch. Hashtag snackwave pop-up yuccie live-edge lo-fi. Lumbersexual roof party pug kale chips thundercats fam. Prism snackwave tbh cardigan williamsburg. Cardigan affogato meh, godard glossier flexitarian photo booth tote bag literally raw denim lomo ugh.\n\nSkateboard thundercats plaid air plant godard tote bag actually distillery mumblecore gluten-free man bun. Polaroid tattooed godard hexagon, cornhole man braid pitchfork blog. Echo park ugh bespoke seitan. Copper mug swag readymade, chartreuse keffiyeh cold-pressed stumptown mumblecore. Wolf heirloom glossier swag pork belly chambray 8-bit. Roof party four loko snackwave jianbing. Vexillologist schlitz skateboard shoreditch tbh synth.\n\nShabby chic chicharrones vice portland church-key coloring book normcore. Blog 90's williamsburg vinyl cardigan, flannel bitters everyday carry fixie portland lo-fi gochujang. Mumblecore tilde biodiesel craft beer drinking vinegar. Chicharrones mumblecore meditation PBR&B irony occupy pop-up slow-carb taxidermy paleo intelligentsia. Schlitz intelligentsia jean shorts cold-pressed trust fund williamsburg aesthetic echo park." 5 | } -------------------------------------------------------------------------------- /app/content/settings/general.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Homev", 3 | "titleTemplate": "| Bluise", 4 | "welcomeText": "# Yo, I'm Bluise - A Nuxt.js & Netlify CMS boilerplate.\n\nAuthentic cred hoodie hashtag selfies. Raclette banjo cardigan cred. Kitsch XOXO lomo polaroid. Knausgaard put a bird on it four dollar toast leggings typewriter asymmetrical.\n\n\n\n\n\nHIIIIIIIIIIIIIIIIIIIIIIIIIIII", 5 | "logo": "/images/uploads/logo.svg", 6 | "icon": "/images/uploads/logo.png", 7 | "loadingColor": "#0b3765", 8 | "seoDescription": "A quickstart for Nuxt & Netlify CMS", 9 | "seoMetaImage": "" 10 | } -------------------------------------------------------------------------------- /app/content/settings/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bluise", 3 | "shortName": "Bluise", 4 | "description": "A Nuxt.js & Netlify CMS boilerplate.", 5 | "themeColor": "#0b3765", 6 | "backgroundColor": "#ffffff", 7 | "lang": "en" 8 | } 9 | -------------------------------------------------------------------------------- /app/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 29 | 30 | 41 | -------------------------------------------------------------------------------- /app/pages/_page.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 63 | -------------------------------------------------------------------------------- /app/pages/blog/_slug.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 56 | -------------------------------------------------------------------------------- /app/pages/blog/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 85 | 86 | 97 | -------------------------------------------------------------------------------- /app/pages/blog/page/_page.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /app/pages/index.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 102 | -------------------------------------------------------------------------------- /app/shims/index.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | __NUXT__: { state: RootState }; 4 | } 5 | } 6 | 7 | interface Post { 8 | title: string; 9 | slug?: string; 10 | excerpt?: string; 11 | content: string; 12 | publishedAt: string; 13 | featuredImage?: string; 14 | seoDescription?: string; 15 | seoMetaImage?: string; 16 | } 17 | 18 | interface Page { 19 | title: string; 20 | subtitle?: string; 21 | slug?: string; 22 | content: string; 23 | featuredImage?: string; 24 | seoDescription?: string; 25 | seoMetaImage?: string; 26 | } 27 | -------------------------------------------------------------------------------- /app/static/_redirects: -------------------------------------------------------------------------------- 1 | /* /200.html 200 2 | -------------------------------------------------------------------------------- /app/static/admin/config.yml: -------------------------------------------------------------------------------- 1 | # Backend https://www.netlifycms.org/docs/configuration-options/#backend 2 | backend: 3 | name: git-gateway 4 | branch: master 5 | 6 | # Publish mode https://www.netlifycms.org/docs/configuration-options/#publish-mode 7 | publish_mode: editorial_workflow 8 | 9 | # Media and Public Folders https://www.netlifycms.org/docs/configuration-options/#media-and-public-folders 10 | media_folder: 'app/static/images/uploads' # Media files will be stored in the repo under images/uploads 11 | public_folder: '/images/uploads' 12 | 13 | # Collections https://www.netlifycms.org/docs/configuration-options/#collections 14 | collections: 15 | - label: Settings 16 | name: settings 17 | files: 18 | - label: General 19 | name: general 20 | format: json 21 | file: 'app/content/settings/general.json' 22 | fields: 23 | - { label: Title, name: title, widget: string } 24 | - { label: 'Title Template', name: titleTemplate, widget: string } 25 | - { label: 'Welcome Text', name: welcomeText, widget: markdown } 26 | - { label: Logo, name: logo, widget: image } 27 | - { label: Icon, name: icon, widget: image } 28 | - { label: 'Loading Color', name: loadingColor, widget: string, default: '#000000' } 29 | - { label: 'SEO Description', name: seoDescription, widget: text, required: false } 30 | - { label: 'SEO Meta Image', name: seoMetaImage, widget: image, required: false } 31 | - label: Manifest 32 | name: manifest 33 | format: json 34 | file: 'app/content/settings/manifest.json' 35 | fields: 36 | - { label: Name, name: name, widget: string } 37 | - { label: 'Short name', name: shortName, widget: string } 38 | - { label: Description, name: description, widget: text } 39 | - { label: 'Theme color', name: themeColor, widget: string } 40 | - { label: 'Background color', name: backgroundColor, widget: string } 41 | - { label: Lang, name: lang, widget: string, default: en } 42 | 43 | - name: 'pages' 44 | label: 'Pages' 45 | folder: 'app/content/pages' 46 | create: true 47 | slug: '{{slug}}' 48 | format: 'json' 49 | fields: 50 | - { label: Title, name: title, widget: string } 51 | - { label: Subtitle, name: subtitle, widget: text, required: false } 52 | - { label: Content, name: content, widget: markdown } 53 | - { label: 'Featured Image', name: featuredImage, widget: image, required: false } 54 | - { label: 'SEO Description', name: seoDescription, widget: text, required: false } 55 | - { label: 'SEO Meta Image', name: seoMetaImage, widget: image, required: false } 56 | - name: 'blog' 57 | label: 'Blog' 58 | folder: 'app/content/blog' 59 | create: true 60 | slug: '{{slug}}' 61 | format: 'json' 62 | sort: 'published_at:desc' 63 | fields: 64 | - { label: Title, name: title, widget: string } 65 | - { label: 'Published At', name: publishedAt, widget: date, format: 'Do MMM YYYY' } 66 | - { label: Content, name: content, widget: markdown } 67 | - { label: 'Featured Image', name: featuredImage, widget: image, required: false } 68 | - { label: 'SEO Description', name: seoDescription, widget: text, required: false } 69 | - { label: 'SEO Meta Image', name: seoMetaImage, widget: image, required: false } 70 | -------------------------------------------------------------------------------- /app/static/admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Content Manager 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/static/images/uploads/81464027_471079400471586_3271125514441457664_n.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gomah/bluise/38432163c54c1ac10bb9e43ded47de43da138cc4/app/static/images/uploads/81464027_471079400471586_3271125514441457664_n.jpg -------------------------------------------------------------------------------- /app/static/images/uploads/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gomah/bluise/38432163c54c1ac10bb9e43ded47de43da138cc4/app/static/images/uploads/logo.png -------------------------------------------------------------------------------- /app/static/images/uploads/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/static/images/uploads/pakistani-chicken-biryani.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gomah/bluise/38432163c54c1ac10bb9e43ded47de43da138cc4/app/static/images/uploads/pakistani-chicken-biryani.jpg -------------------------------------------------------------------------------- /app/store/index.ts: -------------------------------------------------------------------------------- 1 | import { ActionContext, ActionTree, MutationTree } from 'vuex'; 2 | import { Route } from 'vue-router'; 3 | import Vue from 'vue'; 4 | import { getContent } from '@/utils'; 5 | 6 | export interface State { 7 | perPage: number; 8 | pages: Page[]; 9 | posts: Post[]; 10 | route?: Route; 11 | } 12 | 13 | // Initial State 14 | export const appState = { 15 | perPage: 4, 16 | pages: [], 17 | posts: [], 18 | }; 19 | 20 | export const mutations: MutationTree = { 21 | SET_PAGES: (state, payload: Record): void => { 22 | Vue.set(state, 'pages', payload); 23 | }, 24 | SET_POSTS: (state, payload: Record): void => { 25 | Vue.set(state, 'posts', payload); 26 | }, 27 | }; 28 | 29 | interface Actions extends ActionTree { 30 | GET_PAGES_LIST(context: ActionContext): Promise; 31 | GET_POSTS_LIST(context: ActionContext): Promise; 32 | nuxtServerInit(context: ActionContext): void; 33 | } 34 | 35 | export const actions: Actions = { 36 | async GET_POSTS_LIST({ commit }): Promise { 37 | // Use webpack to search the blog directory matching .json files 38 | const context = await require.context('@/content/blog/', false, /\.json$/); 39 | const posts = await getContent({ context, prefix: 'blog' }); 40 | commit('SET_POSTS', posts); 41 | }, 42 | 43 | async GET_PAGES_LIST({ commit }): Promise { 44 | // Use webpack to search the blog directory matching .json files 45 | const context = await require.context('@/content/pages/', false, /\.json$/); 46 | const pages = await getContent({ 47 | context, 48 | prefix: 'pages', 49 | }); 50 | commit('SET_PAGES', pages); 51 | }, 52 | 53 | async nuxtServerInit({ dispatch }): Promise { 54 | await Promise.all([dispatch('GET_PAGES_LIST'), dispatch('GET_POSTS_LIST')]); 55 | }, 56 | }; 57 | 58 | export const state = (): State => ({ 59 | ...appState, 60 | }); 61 | 62 | export const strict = false; 63 | -------------------------------------------------------------------------------- /app/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 | module.exports = { 8 | theme: {}, 9 | variants: {}, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /app/utils.ts: -------------------------------------------------------------------------------- 1 | export function createExcerpt({ text, length = 150 }: { text: string; length?: number }): string { 2 | return text.split('', length).concat(['...']).join(''); 3 | } 4 | 5 | export async function getContent({ context, prefix }): Promise<{ slug: string; title: string }[]> { 6 | const slugs: string[] = []; 7 | const content: { slug: string; title: string; featuredImage: string }[] = []; 8 | 9 | // Get slugs 10 | for (let index = 0; index < context.keys().length; index += 1) { 11 | const slug = context.keys()[index].replace(/^.\/|.json$/g, ''); 12 | slugs.push(slug); 13 | } 14 | 15 | // Get content 16 | for (let index = 0; index < slugs.length; index += 1) { 17 | const slug = slugs[index]; 18 | 19 | const entry = require(`@/content/${prefix}/${slug}.json`); 20 | 21 | // Add the slug to the post object 22 | Object.assign(entry, { slug }); 23 | 24 | content.push({ 25 | slug, 26 | title: entry.title, 27 | ...(prefix === 'blog' && { 28 | excerpt: createExcerpt({ text: entry.content }), 29 | }), 30 | featuredImage: entry.featuredImage, 31 | }); 32 | } 33 | 34 | return content; 35 | } 36 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | test: { 4 | presets: ['@babel/preset-env'], 5 | plugins: ['@babel/plugin-transform-runtime'], 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /husky.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS', 4 | 'pre-commit': 'yarn test', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'json', 'vue', 'ts'], 3 | preset: 'ts-jest', 4 | moduleDirectories: ['node_modules'], 5 | collectCoverage: true, 6 | collectCoverageFrom: ['/components/**/*.vue', '/pages/*.vue'], 7 | moduleNameMapper: { 8 | '^.+\\.(jpg|jpeg)$': 'jest-static-stubs/jpg', 9 | '^.+\\.(png)$': 'jest-static-stubs/png', 10 | '^.+\\.(svg)$': 'identity-obj-proxy', 11 | '\\.(css)$': '/tests/__mocks__/style.js', 12 | '@/(.*)': '/app/$1', 13 | '~/(.*)': '/app/$1', 14 | '~~/(.*)': '/app/$1', 15 | }, 16 | transform: { 17 | '^.+\\.js$': '/node_modules/babel-jest', 18 | '.*\\.(vue)$': '/node_modules/vue-jest', 19 | }, 20 | setupFiles: [], 21 | snapshotSerializers: ['/node_modules/jest-serializer-vue'], 22 | }; 23 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "dist" 3 | command = "yarn generate" 4 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { Configuration } from '@nuxt/types'; 2 | import fg from 'fast-glob'; 3 | import settings from './app/content/settings/general.json'; 4 | import manifest from './app/content/settings/manifest.json'; 5 | 6 | const nuxtConfig: Configuration = { 7 | /* 8 | ** Headers of the page 9 | */ 10 | head: { 11 | titleTemplate: `%s ${settings.titleTemplate}`, 12 | title: settings.title, 13 | meta: [ 14 | { charset: 'utf-8' }, 15 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 16 | { hid: 'description', name: 'description', content: settings.seoDescription }, 17 | { 18 | hid: 'og:title', 19 | property: 'og:title', 20 | content: `${settings.title} ${settings.titleTemplate}`, 21 | }, 22 | { 23 | hid: 'og:description', 24 | property: 'og:description', 25 | content: settings.seoDescription, 26 | }, 27 | { 28 | hid: 'og:image', 29 | property: 'og:image', 30 | content: settings.seoMetaImage, 31 | }, 32 | ], 33 | script: [{ src: 'https://identity.netlify.com/v1/netlify-identity-widget.js', defer: true }], 34 | link: [ 35 | { 36 | rel: 'preconnect', 37 | href: 'https://d33wubrfki0l68.cloudfront.net', 38 | }, 39 | ], 40 | }, 41 | 42 | srcDir: 'app/', 43 | 44 | /* 45 | ** Customize the progress-bar color 46 | */ 47 | loading: { color: settings.loadingColor }, 48 | 49 | /* 50 | ** Global CSS 51 | */ 52 | css: ['@/assets/css/main.scss'], 53 | 54 | styleResources: { 55 | scss: ['~assets/css/_variables.scss', '~assets/css/_mixins.scss'], 56 | }, 57 | 58 | generate: { 59 | subFolders: false, 60 | 61 | routes: [ 62 | ...fg.sync(['./app/content/blog/**.json', './app/content/pages/**.json']).map(url => ({ 63 | route: url.replace(/^.\/app\/content(\/pages)?|.json$/gi, ''), 64 | payload: require(url), 65 | })), 66 | ], 67 | }, 68 | 69 | /* 70 | ** Plugins to load before mounting the App 71 | */ 72 | plugins: [], 73 | 74 | /* 75 | ** Nuxt.js modules 76 | */ 77 | modules: ['@nuxtjs/pwa', '@nuxtjs/style-resources', '@nuxtjs/markdownit'], 78 | 79 | 80 | markdownit: { 81 | preset: 'default', 82 | 83 | injected: true, 84 | 85 | // Convert '\n' in paragraphs into
86 | breaks: true, 87 | 88 | // Enable HTML tags in source 89 | html: true, 90 | 91 | // Enable some language-neutral replacement + quotes beautification 92 | typographer: true, 93 | }, 94 | 95 | workbox: { 96 | runtimeCaching: [ 97 | { 98 | urlPattern: 'https://d33wubrfki0l68.cloudfront.net/.*', 99 | handler: 'cacheFirst', 100 | }, 101 | ], 102 | }, 103 | 104 | pwa: { 105 | icon: { 106 | iconSrc: `app/static${settings.icon}`, 107 | }, 108 | }, 109 | 110 | manifest: { 111 | name: manifest.name, 112 | short_name: manifest.shortName, 113 | description: manifest.description, 114 | theme_color: manifest.themeColor, 115 | background_color: manifest.backgroundColor, 116 | lang: manifest.lang || 'en', 117 | }, 118 | 119 | meta: { 120 | ogTitle: false, 121 | ogDescription: false, 122 | }, 123 | 124 | // Serve both, the modern bundle