├── .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 | [](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 |
2 |
48 |
49 |
50 |
67 |
68 |
93 |
--------------------------------------------------------------------------------
/app/components/partials/footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
31 |
37 |
38 |
43 |
--------------------------------------------------------------------------------
/app/components/partials/header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
26 |
27 |
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\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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
29 |
30 |
41 |
--------------------------------------------------------------------------------
/app/pages/_page.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ page.title }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
63 |
--------------------------------------------------------------------------------
/app/pages/blog/_slug.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ post.title }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
56 |
--------------------------------------------------------------------------------
/app/pages/blog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Blog
5 |
6 | Slow-carb messenger bag mlkshk fingerstache four dollar toast.
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
{{ post.title }}
21 |
22 |
23 | {{ post.excerpt }}
24 |
25 |
26 |
Read more
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
85 |
86 |
97 |
--------------------------------------------------------------------------------
/app/pages/blog/page/_page.vue:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/app/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Thank you - we'll be in touch shortly.
9 |
10 |
34 |
35 |
36 |
37 |

42 |
43 |
44 |
45 |
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 |
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