├── assets
└── .gitignore
├── static
├── .gitignore
├── favicon.ico
└── README.md
├── layouts
├── reveal.vue
└── default.vue
├── jsconfig.json
├── .editorconfig
├── .eslintrc.js
├── plugins
└── README.md
├── middleware
└── README.md
├── store
└── README.md
├── README.md
├── pages
├── posts
│ ├── _slug.vue
│ └── _y
│ │ └── _mo
│ │ └── _slug.vue
├── index.vue
├── _pageNumber.vue
└── reveal
│ └── _path.vue
├── express
└── routes
│ └── index.js
├── package.json
├── .gitignore
├── components
├── PostQuery.vue
├── PostTeaser.vue
├── Pagination.vue
└── PostFull.vue
├── nuxt.config.js
└── prebuild
└── index.js
/assets/.gitignore:
--------------------------------------------------------------------------------
1 | /data/
--------------------------------------------------------------------------------
/static/.gitignore:
--------------------------------------------------------------------------------
1 | /data/
2 | /media/
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patarapolw/blog-reveal.nuxt/master/static/favicon.ico
--------------------------------------------------------------------------------
/layouts/reveal.vue:
--------------------------------------------------------------------------------
1 |
2 | div(style="width: 100vw; height: 100vh;")
3 | #global(style="display: none")
4 | .reveal
5 | .slides
6 | nuxt
7 |
8 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "~/*": ["./*"],
6 | "@/*": ["./*"],
7 | "~~/*": ["./*"],
8 | "@@/*": ["./*"]
9 | }
10 | },
11 | "exclude": ["node_modules", ".nuxt", "dist"]
12 | }
13 |
--------------------------------------------------------------------------------
/.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 | env: {
4 | browser: true,
5 | node: true
6 | },
7 | parserOptions: {
8 | parser: 'babel-eslint'
9 | },
10 | extends: [
11 | '@nuxtjs',
12 | 'plugin:nuxt/recommended'
13 | ],
14 | // add your custom rules here
15 | rules: {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/plugins/README.md:
--------------------------------------------------------------------------------
1 | # PLUGINS
2 |
3 | **This directory is not required, you can delete it if you don't want to use it.**
4 |
5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application.
6 |
7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).
8 |
--------------------------------------------------------------------------------
/middleware/README.md:
--------------------------------------------------------------------------------
1 | # MIDDLEWARE
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 application middleware.
6 | Middleware let you define custom functions that can be run before rendering either a page or a group of pages.
7 |
8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).
9 |
--------------------------------------------------------------------------------
/store/README.md:
--------------------------------------------------------------------------------
1 | # STORE
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 Vuex Store files.
6 | Vuex Store option is implemented in the Nuxt.js framework.
7 |
8 | Creating a file in this directory automatically activates the option in the framework.
9 |
10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).
11 |
--------------------------------------------------------------------------------
/static/README.md:
--------------------------------------------------------------------------------
1 | # STATIC
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 static files.
6 | Each file inside this directory is mapped to `/`.
7 | Thus you'd want to delete this README.md before deploying to production.
8 |
9 | Example: `/static/robots.txt` is mapped as `/robots.txt`.
10 |
11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # blog-reveal.nuxt
2 |
3 | > Blog-reveal creator, powered by Nuxt
4 |
5 | ## Usage
6 |
7 | Create `.env` with
8 |
9 | ```env
10 | ROOT=
11 | ```
12 |
13 | ## Build Setup
14 |
15 | ``` bash
16 | # install dependencies
17 | yarn install
18 |
19 | # serve with hot reload at localhost:3000
20 | yarn dev
21 |
22 | # build for production and launch server
23 | yarn build
24 | yarn start
25 |
26 | # generate static project
27 | yarn generate
28 | ```
29 |
30 | For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
31 |
--------------------------------------------------------------------------------
/pages/posts/_slug.vue:
--------------------------------------------------------------------------------
1 |
2 | post-full(:p="post")
3 |
4 |
5 |
29 |
--------------------------------------------------------------------------------
/pages/posts/_y/_mo/_slug.vue:
--------------------------------------------------------------------------------
1 |
2 | post-full(:p="post")
3 |
4 |
5 |
29 |
--------------------------------------------------------------------------------
/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | b-navbar.is-light(fixed-top)
4 | template(slot="brand")
5 | b-navbar-item(tag="nuxt-link" to="/") {{config.title}}
6 | template(slot="end")
7 | form.control(style="margin: 10px; margin-right: 2em" @submit.prevent="doSearch(q)")
8 | input.input.is-pulled-right.is-rounded(type="search" placeholder="Type and Enter to Search" style="width: 300px"
9 | v-model="q")
10 | .columns
11 | .column.is-three-fifths-desktop.is-offset-one-fifth-desktop
12 | nuxt
13 |
14 |
15 |
29 |
--------------------------------------------------------------------------------
/express/routes/index.js:
--------------------------------------------------------------------------------
1 | import { Router } from 'express'
2 | import bodyParser from 'body-parser'
3 |
4 | import { config, loadDb } from '../../prebuild'
5 |
6 | const app = Router()
7 | const db = loadDb()
8 |
9 | app.use(bodyParser.json())
10 |
11 | app.get('/data/config.json', (req, res) => {
12 | res.send(config)
13 | })
14 |
15 | app.post('/data/search.json', async (req, res, next) => {
16 | try {
17 | const { q, proj, sort, skip, limit } = req.body
18 | let c = db.find(
19 | q,
20 | proj
21 | )
22 | if (sort) {
23 | c = c.sort(sort)
24 | }
25 | if (skip) {
26 | c = c.skip(skip)
27 | }
28 | if (limit) {
29 | c = c.limit(limit)
30 | }
31 |
32 | res.json(await c)
33 | } catch (e) {
34 | next(e)
35 | }
36 | })
37 |
38 | module.exports = app
39 |
--------------------------------------------------------------------------------
/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 | post-query(:posts="posts" :count="count")
3 |
4 |
5 |
33 |
--------------------------------------------------------------------------------
/pages/_pageNumber.vue:
--------------------------------------------------------------------------------
1 |
2 | post-query(:posts="posts" :count="count")
3 |
4 |
5 |
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog-reveal.nuxt",
3 | "version": "1.0.0",
4 | "description": "Blog-reveal creator, powered by Nuxt",
5 | "author": "Pacharapol Withayasakpunt",
6 | "private": true,
7 | "scripts": {
8 | "dev": "nuxt",
9 | "build": "nuxt build",
10 | "start": "nuxt start",
11 | "generate": "nuxt generate",
12 | "lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
13 | },
14 | "dependencies": {
15 | "@nuxtjs/axios": "^5.3.6",
16 | "@nuxtjs/dotenv": "^1.4.0",
17 | "cors": "^2.8.5",
18 | "dayjs": "^1.8.19",
19 | "dot-prop": "^5.2.0",
20 | "dotenv": "^8.2.0",
21 | "express": "^4.17.1",
22 | "fast-glob": "^3.1.1",
23 | "fs-extra": "^8.1.0",
24 | "gray-matter": "^4.0.2",
25 | "hyperscript": "^2.0.2",
26 | "js-yaml": "^3.13.1",
27 | "nedb-promises": "^4.0.1",
28 | "nuxt": "^2.0.0",
29 | "nuxt-buefy": "^0.3.2",
30 | "nuxt-express-module": "^0.0.11",
31 | "nuxt-payload-extractor": "^0.0.14",
32 | "scope-css": "^1.2.1",
33 | "showdown": "^1.9.1"
34 | },
35 | "devDependencies": {
36 | "@nuxtjs/eslint-config": "^1.0.1",
37 | "@nuxtjs/eslint-module": "^1.0.0",
38 | "babel-eslint": "^10.0.1",
39 | "eslint": "^6.1.0",
40 | "eslint-plugin-nuxt": ">=0.4.2",
41 | "pug": "^2.0.4",
42 | "pug-plain-loader": "^1.0.0",
43 | "sass": "^1.25.0",
44 | "sass-loader": "^8.0.2"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | /logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 |
62 | # parcel-bundler cache (https://parceljs.org/)
63 | .cache
64 |
65 | # next.js build output
66 | .next
67 |
68 | # nuxt.js build output
69 | .nuxt
70 |
71 | # Nuxt generate
72 | dist
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless
79 |
80 | # IDE / Editor
81 | .idea
82 |
83 | # Service worker
84 | sw.*
85 |
86 | # Mac OSX
87 | .DS_Store
88 |
89 | # Vim swap files
90 | *.swp
91 |
--------------------------------------------------------------------------------
/components/PostQuery.vue:
--------------------------------------------------------------------------------
1 |
2 | section(style="margin-top: 1em")
3 | h1.title(v-if="tag" style="margin-bottom: 0.5em")
4 | span Tag:
5 | strong {{tag}}
6 | post-teaser(v-for="p in posts" :key="p.slug" :p="p")
7 | pagination(:page="pageNumber" :count="Math.ceil(count / 5)" :base="tag ? '/tag/': '/'")
8 |
9 |
10 |
58 |
--------------------------------------------------------------------------------
/components/PostTeaser.vue:
--------------------------------------------------------------------------------
1 |
2 | section.card(style="margin-bottom: 1em")
3 | .card-content
4 | h2.title
5 | nuxt-link.hoverable(:to="url") {{p.frontmatter.title}}
6 | div(style="height: 1.5em; margin-bottom: 2em;")
7 | small.is-pulled-right {{dateString}}
8 |
9 | .columns(v-if="p.frontmatter.image" style="flex-direction: row-reverse;")
10 | .column.is-two-fifths
11 | figure.image
12 | img.lazyload(:alt="p.frontmatter.title" :data-src="p.frontmatter.image")
13 | .column
14 | .content(v-html="toHtml(p.excerpt)")
15 | .content(v-else v-html="toHtml(p.excerpt)")
16 |
17 | div(style="height: 20px")
18 | nuxt-link.button.is-outlined.is-danger.is-pulled-right(:to="url") Read more
19 |
20 |
21 |
55 |
56 |
61 |
--------------------------------------------------------------------------------
/nuxt.config.js:
--------------------------------------------------------------------------------
1 | import { config } from './prebuild'
2 |
3 | export default {
4 | mode: 'universal',
5 | /*
6 | ** Headers of the page
7 | */
8 | head: {
9 | titleTemplate: config.title ? `%s - ${config.title}` : '%s',
10 | meta: [
11 | { charset: 'utf-8' },
12 | { name: 'viewport', content: 'width=device-width, initial-scale=1' },
13 | { hid: 'description', name: 'description', content: config.description }
14 | ],
15 | link: [
16 | { rel: 'icon', type: 'image/x-icon', href: '/media/favicon.ico' }
17 | ]
18 | },
19 | /*
20 | ** Customize the progress-bar color
21 | */
22 | loading: { color: '#fff' },
23 | /*
24 | ** Global CSS
25 | */
26 | css: [
27 | ],
28 | /*
29 | ** Plugins to load before mounting the App
30 | */
31 | plugins: [
32 | ],
33 | /*
34 | ** Nuxt.js dev-modules
35 | */
36 | buildModules: [
37 | // Doc: https://github.com/nuxt-community/eslint-module
38 | '@nuxtjs/eslint-module'
39 | ],
40 | /*
41 | ** Nuxt.js modules
42 | */
43 | modules: [
44 | // Doc: https://buefy.github.io/#/documentation
45 | 'nuxt-buefy',
46 | // Doc: https://axios.nuxtjs.org/usage
47 | '@nuxtjs/axios',
48 | // Doc: https://github.com/nuxt-community/dotenv-module
49 | '@nuxtjs/dotenv',
50 | 'nuxt-payload-extractor',
51 | 'nuxt-express-module'
52 | ],
53 | /*
54 | ** Axios module configuration
55 | ** See https://axios.nuxtjs.org/options
56 | */
57 | axios: {
58 | },
59 | /*
60 | ** Build configuration
61 | */
62 | build: {
63 | /*
64 | ** You can extend webpack config here
65 | */
66 | extend (config, ctx) {
67 | }
68 | },
69 | hooks: {
70 | // build: {
71 | // done () {
72 | // rawServer.close()
73 | // }
74 | // },
75 | // generate: {
76 | // done () {
77 | // rawServer.close()
78 | // }
79 | // }
80 | },
81 | env: {
82 | config: JSON.stringify(config)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/components/Pagination.vue:
--------------------------------------------------------------------------------
1 |
2 | section
3 | nav.pagination.is-centered(v-if="count > 1" role="navigation" aria-label="pagination")
4 | nuxt-link.pagination-previous(v-if="prevCount > 0" :to="formatTo(page - 1)") <
5 | button.pagination-previous(v-else disabled) <
6 |
7 | nuxt-link.pagination-next(v-if="nextCount > 0" :to="formatTo(count)") >
8 | button.pagination-next(v-else disabled) >
9 |
10 | ul.pagination-list
11 | li(v-if="prevCount >= 2")
12 | nuxt-link.pagination-link(aria-label="Goto page 1" :to="formatTo(1)") 1
13 | li(v-if="prevCount > 2")
14 | span.pagination-ellipsis …
15 | li(v-if="prevCount >= 1")
16 | nuxt-link.pagination-link(:aria-label="'Goto page ' + (page - 1)" :to="formatTo(page - 1)") {{page - 1}}
17 | li
18 | a.pagination-link.is-current(:aria-label="'Page ' + page" aria-current="page") {{0}}
19 | li(v-if="nextCount >= 1")
20 | nuxt-link.pagination-link(:aria-label="'Goto page ' + (page + 1)" :to="formatTo(page + 1)") {{page + 1}}
21 | li(v-if="nextCount > 2")
22 | span.pagination-ellipsis …
23 | li(v-if="nextCount >= 2")
24 | nuxt-link.pagination-link(:aria-label="'Goto page ' + count" :to="formatTo(count)") 1
25 |
26 |
27 |
63 |
--------------------------------------------------------------------------------
/components/PostFull.vue:
--------------------------------------------------------------------------------
1 |
2 | .card(style="margin-top: 1em")
3 | .card-content(v-if="!['reveal', 'slides'].includes(p.type)")
4 | h1.title {{p.frontmatter.title}}
5 | div(style="height: 1.5em; margin-bottom: 2em;")
6 | small.is-pulled-right {{dateString}}
7 |
8 | .media(v-if="p.frontmatter.image")
9 | figure.image
10 | img.lazyload(:alt="p.frontmatter.title" :src="p.frontmatter.image")
11 |
12 | .content(v-html="toHtml(p.content)")
13 | .card-content(v-else)
14 | iframe.reveal-iframe(frameborder="0" :src="revealUrl")
15 |
16 |
17 |
69 |
70 |
76 |
--------------------------------------------------------------------------------
/prebuild/index.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 |
3 | import fs from 'fs-extra'
4 | import dotenv from 'dotenv'
5 | import matter from 'gray-matter'
6 | import glob from 'fast-glob'
7 | import DataStore from 'nedb-promises'
8 | import yaml from 'js-yaml'
9 | import dayjs from 'dayjs'
10 |
11 | dotenv.config()
12 |
13 | export function loadDb () {
14 | const db = new DataStore()
15 |
16 | glob.sync('**/*.md', {
17 | cwd: path.join(process.env.ROOT, 'data')
18 | }).map((f) => {
19 | // eslint-disable-next-line prefer-const
20 | let { data, content, excerpt } = matter(fs.readFileSync(path.join(process.env.ROOT, 'data', f), 'utf8'), {
21 | engines: {
22 | yaml: s => yaml.safeLoad(s, {
23 | schema: yaml.JSON_SCHEMA
24 | })
25 | },
26 | excerpt_separator: ''
27 | })
28 |
29 | const ps = f.split('/')
30 | let slug = ps[ps.length - 1].replace(/\.md$/, '');
31 | (() => {
32 | const m = /^(\d{4}-\d{2}-\d{2})-(.+)$/.exec(slug)
33 | if (m) {
34 | data.date = data.date || m[1]
35 | slug = m[2]
36 | }
37 | })()
38 |
39 | const type = data.type || ps[0]
40 |
41 | if (!data.title) {
42 | const m = /^.*?# ([^\n]+)(.+)$/s.exec(content)
43 | if (m) {
44 | data.title = m[1]
45 | if (!['reveal', 'slides'].includes(type)) {
46 | content = m[2]
47 | }
48 | }
49 | }
50 |
51 | const epoch = data.date ? customDateStringToEpoch(data.date) : undefined
52 | const m = epoch ? dayjs(epoch) : undefined
53 |
54 | excerpt = (excerpt || content).replace(/<[^>]+>?/g, '').substr(0, 140)
55 |
56 | db.insert({
57 | path: f,
58 | type,
59 | slug,
60 | excerpt,
61 | frontmatter: data,
62 | epoch,
63 | content,
64 | y: m ? m.format('YYYY') : undefined,
65 | mo: m ? m.format('MM') : undefined
66 | })
67 | })
68 |
69 | return db
70 | }
71 |
72 | // eslint-disable-next-line import/no-mutable-exports
73 | export let config = {}
74 | if (fs.existsSync(path.join(process.env.ROOT, 'config.json'))) {
75 | config = require(path.join(process.env.ROOT, 'config.json'))
76 | }
77 |
78 | if (fs.existsSync(path.join(process.env.ROOT, 'data/media'))) {
79 | fs.copySync(
80 | path.join(process.env.ROOT, 'data/media'),
81 | 'static/media'
82 | )
83 | }
84 |
85 | function customDateStringToEpoch (date) {
86 | if (!date) {
87 | return undefined
88 | }
89 |
90 | /**
91 | * Moment will default timezone to local if not specified, unlike Date.parse
92 | *
93 | * https://momentjs.com/docs/#/parsing/
94 | *
95 | * See #please-read
96 | */
97 | let m = dayjs(date, [
98 | 'YYYY-MM-DD HH:MM ZZ',
99 | 'YYYY-MM-DD ZZ',
100 | 'YYYY-MM-DD HH:MM',
101 | 'YYYY-MM-DD'
102 | ])
103 |
104 | if (m.isValid()) {
105 | /**
106 | * moment().unix() is in seconds
107 | */
108 | return m.unix() * 1000
109 | }
110 |
111 | m = dayjs(date)
112 |
113 | if (m.isValid()) {
114 | return m.unix() * 1000
115 | }
116 |
117 | return undefined
118 | }
119 |
--------------------------------------------------------------------------------
/pages/reveal/_path.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 |
4 |
5 |
216 |
217 |
228 |
--------------------------------------------------------------------------------