diff --git a/.eslintignore b/.eslintignore
index 2cb93f7..487598e 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,4 +1,5 @@
*.*
!*.js
!*.ts
+!*.vue
node_modules
\ No newline at end of file
diff --git a/data/config.json b/data/config.json
index 81aec5c..becc397 100644
--- a/data/config.json
+++ b/data/config.json
@@ -3,7 +3,7 @@
"tabs": [
{
"name": "千字文",
- "to": "/posts/千字文"
+ "to": "posts/2019/08/千字文"
},
{
"name": "Resources",
@@ -14,8 +14,5 @@
"url": "https://github.com/patarapolw/zhdiary"
}
],
- "fullUrl": "https://zhdiary.polvcode.dev",
- "author": {
- "name": "Pacharapol Withayasakpunt"
- }
+ "fullUrl": "https://zhdiary.polvcode.dev"
}
\ No newline at end of file
diff --git a/web/backend/scripts/prepare.ts b/web/backend/scripts/prepare.ts
index 3008344..3e6e5eb 100644
--- a/web/backend/scripts/prepare.ts
+++ b/web/backend/scripts/prepare.ts
@@ -47,6 +47,8 @@ const db = new DataStore('assets/data.nedb')
...data,
date: formatDate(data.date),
filename: f,
+ type,
+ slug: f.replace(/^slides\//, '').replace(/\.md$/, ''),
h,
excerpt: computedExcerpt,
slides
@@ -66,14 +68,19 @@ const db = new DataStore('assets/data.nedb')
computedContent = computedContent.substr(computedExcerpt.length)
}
- if (data.date) {
- console.log(formatDate(data.date))
- }
-
await db.insert({
...data,
date: formatDate(data.date),
filename: f,
+ type: f.split('/')[0],
+ slug: f.startsWith('posts/blog/')
+ ? f.replace(/^posts\/blog\//, '').replace(/\.md$/, '')
+ : f.startsWith('posts/')
+ ? (() => {
+ const ff = f.split('/')
+ return ff[ff.length - 1].replace(/\.md$/, '')
+ })()
+ : f.replace(/^[^/]+?\//, '').replace(/\.md$/, ''),
h,
excerpt: computedExcerpt,
remaining: computedContent
diff --git a/web/backend/src/api/data.ts b/web/backend/src/api/data.ts
index a221e34..4b18af8 100644
--- a/web/backend/src/api/data.ts
+++ b/web/backend/src/api/data.ts
@@ -24,30 +24,30 @@ dataRouter.get('/*', (req, res) => {
}
})
-dataRouter.post('/', async (req, res, next) => {
+dataRouter.post('/:type', async (req, res, next) => {
try {
const { q, cond = {}, project, offset, limit } = req.body
const $and = [
- { filename: { $regex: /^posts\// } },
+ { type: req.params.type },
cond
- ] as any[]
+ ]
if (q) {
const parser = new QSearch({
dialect: 'nedb',
schema: {
title: {},
- filename: {},
+ slug: {},
excerpt: {},
date: { type: 'date' }
}
})
- $and.push(parser.parse(q))
+ $and.push(parser.parse(q).cond)
}
const count = await db.count({ $and })
- let c = db.find({ $and }, project).skip(offset || 0)
+ let c = db.find({ $and }, project).sort({ date: -1 }).skip(offset || 0)
if (limit !== null) {
c = c.limit(limit || 5)
diff --git a/web/backend/src/utils/make-html.ts b/web/backend/src/utils/make-html.ts
index 1f286e0..63e30a4 100644
--- a/web/backend/src/utils/make-html.ts
+++ b/web/backend/src/utils/make-html.ts
@@ -9,6 +9,8 @@ export default class MakeHtml {
})
constructor (public id = 'el-' + Math.random().toString(36).substr(2)) {
+ this.md.setFlavor('github')
+
this.md.addExtension({
type: 'lang',
filter (text) {
diff --git a/web/frontend/src/components/Empty.vue b/web/frontend/src/components/Empty.vue
new file mode 100644
index 0000000..5840535
--- /dev/null
+++ b/web/frontend/src/components/Empty.vue
@@ -0,0 +1,11 @@
+
+.container.m-md
+ h4.title.is-4 Sorry, but no post can be found.
+
+
+
diff --git a/web/frontend/src/components/PostHeader.vue b/web/frontend/src/components/PostHeader.vue
index 39af601..672bc0f 100644
--- a/web/frontend/src/components/PostHeader.vue
+++ b/web/frontend/src/components/PostHeader.vue
@@ -1,8 +1,9 @@
-.post-meta.mb-sm
- .post-meta-author
- a.mr-md(:href="author.link" style="width: 24px; min-width: 24px"): img(:src="author.avatar" style="border-radius: 50%;")
- a.mr-md(:href="author.link") {{author.login}}
+.post-meta(style="margin-bottom: 0.5em;")
+ .post-meta-author(v-if="author")
+ a(:href="author.link" style="width: 24px; min-width: 24px")
+ img(:src="author.avatar" style="border-radius: 50%; margin-right: 1em;")
+ a.mr-md(:href="author.link" style="margin-right: 1em;") {{author.login}}
div(style="flex-grow: 1")
div {{dateString}}
@@ -17,10 +18,10 @@ import { config } from '../global'
export default class PostHeader extends Vue {
@Prop({ required: true }) header!: any
- author = config.author || {}
+ author = config.author
get dateString () {
- return this.header.date ? dayjs(this.header.date).format("ddd D MMMM YYYY") : ''
+ return this.header.date ? dayjs(this.header.date).format('ddd D MMMM YYYY') : ''
}
}
diff --git a/web/frontend/src/components/PostTeaser.vue b/web/frontend/src/components/PostTeaser.vue
index e06e8c9..cce0fcf 100644
--- a/web/frontend/src/components/PostTeaser.vue
+++ b/web/frontend/src/components/PostTeaser.vue
@@ -1,12 +1,12 @@
-.card.my-lg.blog-post
+.card.blog-post(style="margin-top: 1em; margin-bottom: 1em")
.card-content
PostHeader(:header="header")
.post-content
.image-teaser(v-if="header.image")
img(:src="header.image")
h2.title {{header.title}}
- .content(v-html="header.excerpt")
+ .content(v-html="header.excerpt")
div(style="display: flex; justify-content: flex-end")
router-link.button.is-danger.is-outlined(:to="url") Read more
@@ -25,7 +25,7 @@ export default class PostTeaser extends Vue {
@Prop({ required: true }) header!: any
get url () {
- return this.header.filename.replace(/\.md$/, '')
+ return 'posts/' + this.header.slug
}
}
@@ -36,10 +36,12 @@ export default class PostTeaser extends Vue {
margin: 0;
max-width: 80vw;
}
+
.post-content {
width: 100%;
overflow: visible;
}
+
.image-teaser {
width: calc(100% + 3rem);
margin-top: 1rem;
@@ -47,6 +49,7 @@ export default class PostTeaser extends Vue {
margin-left: -1.5rem;
margin-right: -1.5rem;
}
+
@media only screen and (min-width: 800px) {
.image-teaser {
width: 100%;
@@ -55,6 +58,7 @@ export default class PostTeaser extends Vue {
float: right;
margin: 1rem;
}
+
.post-content {
overflow: auto;
}
diff --git a/web/frontend/src/layouts/default.vue b/web/frontend/src/layouts/default.vue
index d9b26ff..330cddb 100644
--- a/web/frontend/src/layouts/default.vue
+++ b/web/frontend/src/layouts/default.vue
@@ -15,7 +15,7 @@ section
:target="t.to ? '' : '_blank'"
) {{t.name}}
template(slot="end")
- form.field.m-sm.px-md(@submit.prevent="onSearch")
+ form.field.m-sm.px-md(@submit.prevent="onSearch" style="margin: 0.4rem; padding-left: 0.5rem; padding-right: 0.5rem")
.control.has-icons-right
input.input.is-rounded(type="search" placeholder="Search" v-model="q")
span.icon.is-right
@@ -34,20 +34,21 @@ import { normalizeArray } from '../utils'
@Component({
metaInfo () {
- const url = this.config.fullUrl + this.$route.path;
+ const url = this.config.fullUrl + this.$route.path
return {
+ titleTemplate: '%s - ZhDiary',
link: [
{
rel: 'canonical',
- href: url,
- },
+ href: url
+ }
],
meta: [
{
hid: 'og:url',
property: 'og:url',
content: url
- },
+ }
]
}
}
@@ -56,16 +57,17 @@ export default class DefaultLayout extends Vue {
config = config
q = ''
- mounted() {
- this.q = normalizeArray(this.$route.query.q) || "";
+ mounted () {
+ this.q = normalizeArray(this.$route.query.q) || ''
}
- onSearch() {
+
+ onSearch () {
this.$router.push({
- path: "/",
+ path: '/',
query: {
q: this.q
}
- });
+ })
}
}
diff --git a/web/frontend/src/router/index.ts b/web/frontend/src/router/index.ts
index 3cbbe6a..cbb498f 100644
--- a/web/frontend/src/router/index.ts
+++ b/web/frontend/src/router/index.ts
@@ -33,6 +33,13 @@ const router = new VueRouter({
meta: {
layout: 'default'
}
+ },
+ {
+ path: '/posts/*',
+ component: () => import('../views/PostView.vue'),
+ meta: {
+ layout: 'default'
+ }
}
]
})
diff --git a/web/frontend/src/views/PostView.vue b/web/frontend/src/views/PostView.vue
new file mode 100644
index 0000000..efbc99e
--- /dev/null
+++ b/web/frontend/src/views/PostView.vue
@@ -0,0 +1,148 @@
+
+section
+ .blog-post(v-if="post.excerpt")
+ .card(style="margin-top: 1rem; margin-bottom: 1rem;")
+ .card-content.content
+ PostHeader(:header="post")
+ h1.title {{post.title}}
+ .image-full(v-if="post.image")
+ img(:src="post.image" :alt="post.title")
+ div(v-html="post.excerpt + post.remaining")
+ div(style="word-break: break-word")
+ span(style="margin-right: 0.5rem;") Tag:
+ span(v-for="t in post.tag || []" :key="t" style="margin-right: 0.5rem;")
+ router-link(:to="'/tag/' + t") {{t}}
+ .card.my-lg
+ .card-content
+ vue-disqus(v-if="disqus" :shortname="disqus" :identifier="$route.path")
+ Empty(v-else)
+
+
+
+
+
diff --git a/web/frontend/src/views/QueryView.vue b/web/frontend/src/views/QueryView.vue
index bd31c34..f162725 100644
--- a/web/frontend/src/views/QueryView.vue
+++ b/web/frontend/src/views/QueryView.vue
@@ -1,13 +1,13 @@
section
- section.m-lg(v-if="tag")
+ section(v-if="tag" style="margin: 1rem;")
h2.title.is-2
- span.mr-sm Tag:
+ span(style="margin-right: 0.5rem;") Tag:
span(style="font-weight: 400") {{tag}}
section(v-if="posts.length > 0")
- PostTeaser(v-for="p in posts" :header="p" :key="p.filename")
- b-pagination.my-lg.mx-sm(:current.sync="page" :total="count" :per-page="5"
- size="default" rounded)
+ PostTeaser(v-for="p in posts" :header="p" :key="p.slug")
+ b-pagination(:current.sync="page" :total="count" :per-page="5"
+ size="default" rounded style="margin-top: 1rem;")
div(v-else)
Empty
@@ -16,17 +16,16 @@ section
import { Vue, Component } from 'vue-property-decorator'
import PostTeaser from '../components/PostTeaser.vue'
-import { api, normalizeArray } from '../utils'
+import Empty from '../components/Empty.vue'
+import { api } from '../utils'
@Component({
components: {
- PostTeaser
+ PostTeaser,
+ Empty
},
- beforeRouteEnter (to, from, next) {
- console.log(to)
- const { query, params } = to
-
- api.post('/data/', {
+ beforeRouteEnter ({ query, params }, from, next) {
+ api.post('/data/posts', {
q: query.q,
cond: params.tag ? {
tag: params.tag
@@ -35,7 +34,7 @@ import { api, normalizeArray } from '../utils'
limit: 5,
project: {
title: 1,
- filename: 1,
+ slug: 1,
excerpt: 1,
date: 1,
tag: 1
@@ -48,7 +47,7 @@ import { api, normalizeArray } from '../utils'
})
},
beforeRouteUpdate ({ query, params }, from, next) {
- api.post('/data/', {
+ api.post('/data/posts', {
q: query.q,
cond: params.tag ? {
tag: params.tag
@@ -57,7 +56,7 @@ import { api, normalizeArray } from '../utils'
limit: 5,
project: {
title: 1,
- filename: 1,
+ slug: 1,
excerpt: 1,
date: 1,
tag: 1
@@ -67,6 +66,58 @@ import { api, normalizeArray } from '../utils'
this.$set(this, 'count', data.count)
next()
})
+ },
+ metaInfo () {
+ const { tag, page } = this.$route.params
+ const p0 = this.posts[0] || {}
+ const title = (tag ? `Tag: ${tag}` : 'Posts') + (page ? ` - Page ${page}` : '')
+ const description = p0.excerpt ? (() => {
+ const div = document.createElement('div')
+ div.innerHTML = p0.excerpt
+ return div.innerText
+ })() : ''
+
+ return {
+ title,
+ meta: [
+ { vmid: 'description', name: 'description', content: description },
+ {
+ hid: 'og:title',
+ property: 'og:title',
+ content: p0.title
+ },
+ {
+ hid: 'og:description',
+ property: 'og:description',
+ content: description
+ },
+ {
+ hid: 'og:image',
+ property: 'og:image',
+ content: p0.image
+ },
+ {
+ hid: 'twitter:title',
+ property: 'twitter:title',
+ content: p0.title
+ },
+ {
+ hid: 'twitter:description',
+ property: 'twitter:description',
+ content: description
+ },
+ {
+ hid: 'twitter:image',
+ property: 'twitter:image',
+ content: p0.image
+ },
+ {
+ hid: 'twitter:card',
+ property: 'twitter:card',
+ content: 'summary_large_image'
+ }
+ ]
+ }
}
})
export default class QueryView extends Vue {
@@ -101,9 +152,5 @@ export default class QueryView extends Vue {
})
}
}
-
- get q () {
- return normalizeArray(this.$route.query.q)
- }
}