This commit is contained in:
Pacharapol Withayasakpunt committed 2020-02-28 13:03:53 +07:00
1 parent 3bc2a943bd
commit e3fbe853df
12 files changed
+279 -52

No files matched your search

+1
View File
@@ -1,4 +1,5 @@
*.* *.*
!*.js !*.js
!*.ts !*.ts
!*.vue
node_modules node_modules
+2 -5
View File
@@ -3,7 +3,7 @@
"tabs": [ "tabs": [
{ {
"name": "千字文", "name": "千字文",
"to": "/posts/千字文" "to": "posts/2019/08/千字文"
}, },
{ {
"name": "Resources", "name": "Resources",
@@ -14,8 +14,5 @@
"url": "https://github.com/patarapolw/zhdiary" "url": "https://github.com/patarapolw/zhdiary"
} }
], ],
"fullUrl": "https://zhdiary.polvcode.dev", "fullUrl": "https://zhdiary.polvcode.dev"
"author": {
"name": "Pacharapol Withayasakpunt"
}
} }
+11 -4
View File
@@ -47,6 +47,8 @@ const db = new DataStore('assets/data.nedb')
...data, ...data,
date: formatDate(data.date), date: formatDate(data.date),
filename: f, filename: f,
type,
slug: f.replace(/^slides\//, '').replace(/\.md$/, ''),
h, h,
excerpt: computedExcerpt, excerpt: computedExcerpt,
slides slides
@@ -66,14 +68,19 @@ const db = new DataStore('assets/data.nedb')
computedContent = computedContent.substr(computedExcerpt.length) computedContent = computedContent.substr(computedExcerpt.length)
} }
if (data.date) {
console.log(formatDate(data.date))
}
await db.insert({ await db.insert({
...data, ...data,
date: formatDate(data.date), date: formatDate(data.date),
filename: f, 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, h,
excerpt: computedExcerpt, excerpt: computedExcerpt,
remaining: computedContent remaining: computedContent
+6 -6
View File
@@ -24,30 +24,30 @@ dataRouter.get('/*', (req, res) => {
} }
}) })
dataRouter.post('/', async (req, res, next) => { dataRouter.post('/:type', async (req, res, next) => {
try { try {
const { q, cond = {}, project, offset, limit } = req.body const { q, cond = {}, project, offset, limit } = req.body
const $and = [ const $and = [
{ filename: { $regex: /^posts\// } }, { type: req.params.type },
cond cond
] as any[] ]
if (q) { if (q) {
const parser = new QSearch({ const parser = new QSearch({
dialect: 'nedb', dialect: 'nedb',
schema: { schema: {
title: {}, title: {},
filename: {}, slug: {},
excerpt: {}, excerpt: {},
date: { type: 'date' } date: { type: 'date' }
} }
}) })
$and.push(parser.parse(q)) $and.push(parser.parse(q).cond)
} }
const count = await db.count({ $and }) 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) { if (limit !== null) {
c = c.limit(limit || 5) c = c.limit(limit || 5)
+2
View File
@@ -9,6 +9,8 @@ export default class MakeHtml {
}) })
constructor (public id = 'el-' + Math.random().toString(36).substr(2)) { constructor (public id = 'el-' + Math.random().toString(36).substr(2)) {
this.md.setFlavor('github')
this.md.addExtension({ this.md.addExtension({
type: 'lang', type: 'lang',
filter (text) { filter (text) {
+11
View File
@@ -0,0 +1,11 @@
<template lang="pug">
.container.m-md
h4.title.is-4 Sorry, but no post can be found.
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
@Component
export default class Empty extends Vue {}
</script>
+7 -6
View File
@@ -1,8 +1,9 @@
<template lang="pug"> <template lang="pug">
.post-meta.mb-sm .post-meta(style="margin-bottom: 0.5em;")
.post-meta-author .post-meta-author(v-if="author")
a.mr-md(:href="author.link" style="width: 24px; min-width: 24px"): img(:src="author.avatar" style="border-radius: 50%;") a(:href="author.link" style="width: 24px; min-width: 24px")
a.mr-md(:href="author.link") {{author.login}} 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(style="flex-grow: 1")
div {{dateString}} div {{dateString}}
</template> </template>
@@ -17,10 +18,10 @@ import { config } from '../global'
export default class PostHeader extends Vue { export default class PostHeader extends Vue {
@Prop({ required: true }) header!: any @Prop({ required: true }) header!: any
author = config.author || {} author = config.author
get dateString () { 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') : ''
} }
} }
</script> </script>
+6 -2
View File
@@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
.card.my-lg.blog-post .card.blog-post(style="margin-top: 1em; margin-bottom: 1em")
.card-content .card-content
PostHeader(:header="header") PostHeader(:header="header")
.post-content .post-content
@@ -25,7 +25,7 @@ export default class PostTeaser extends Vue {
@Prop({ required: true }) header!: any @Prop({ required: true }) header!: any
get url () { get url () {
return this.header.filename.replace(/\.md$/, '') return 'posts/' + this.header.slug
} }
} }
</script> </script>
@@ -36,10 +36,12 @@ export default class PostTeaser extends Vue {
margin: 0; margin: 0;
max-width: 80vw; max-width: 80vw;
} }
.post-content { .post-content {
width: 100%; width: 100%;
overflow: visible; overflow: visible;
} }
.image-teaser { .image-teaser {
width: calc(100% + 3rem); width: calc(100% + 3rem);
margin-top: 1rem; margin-top: 1rem;
@@ -47,6 +49,7 @@ export default class PostTeaser extends Vue {
margin-left: -1.5rem; margin-left: -1.5rem;
margin-right: -1.5rem; margin-right: -1.5rem;
} }
@media only screen and (min-width: 800px) { @media only screen and (min-width: 800px) {
.image-teaser { .image-teaser {
width: 100%; width: 100%;
@@ -55,6 +58,7 @@ export default class PostTeaser extends Vue {
float: right; float: right;
margin: 1rem; margin: 1rem;
} }
.post-content { .post-content {
overflow: auto; overflow: auto;
} }
+12 -10
View File
@@ -15,7 +15,7 @@ section
:target="t.to ? '' : '_blank'" :target="t.to ? '' : '_blank'"
) {{t.name}} ) {{t.name}}
template(slot="end") 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 .control.has-icons-right
input.input.is-rounded(type="search" placeholder="Search" v-model="q") input.input.is-rounded(type="search" placeholder="Search" v-model="q")
span.icon.is-right span.icon.is-right
@@ -34,20 +34,21 @@ import { normalizeArray } from '../utils'
@Component<DefaultLayout>({ @Component<DefaultLayout>({
metaInfo () { metaInfo () {
const url = this.config.fullUrl + this.$route.path; const url = this.config.fullUrl + this.$route.path
return { return {
titleTemplate: '%s - ZhDiary',
link: [ link: [
{ {
rel: 'canonical', rel: 'canonical',
href: url, href: url
}, }
], ],
meta: [ meta: [
{ {
hid: 'og:url', hid: 'og:url',
property: 'og:url', property: 'og:url',
content: url content: url
}, }
] ]
} }
} }
@@ -56,16 +57,17 @@ export default class DefaultLayout extends Vue {
config = config config = config
q = '' q = ''
mounted() { mounted () {
this.q = normalizeArray(this.$route.query.q) || ""; this.q = normalizeArray(this.$route.query.q) || ''
} }
onSearch() {
onSearch () {
this.$router.push({ this.$router.push({
path: "/", path: '/',
query: { query: {
q: this.q q: this.q
} }
}); })
} }
} }
</script> </script>
+7
View File
@@ -33,6 +33,13 @@ const router = new VueRouter({
meta: { meta: {
layout: 'default' layout: 'default'
} }
},
{
path: '/posts/*',
component: () => import('../views/PostView.vue'),
meta: {
layout: 'default'
}
} }
] ]
}) })
+148
View File
@@ -0,0 +1,148 @@
<template lang="pug">
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)
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import { api } from '../utils'
import PostHeader from '../components/PostHeader.vue'
import Empty from '../components/Empty.vue'
@Component<PostView>({
components: {
PostHeader,
Empty
},
beforeRouteEnter ({ params }, from, next) {
api.post('/data/posts', {
cond: {
slug: params.pathMatch
},
limit: 1,
project: {
title: 1,
excerpt: 1,
remaining: 1,
date: 1,
tag: 1
}
}).then(({ data }) => {
next((vm) => {
vm.post = data.data[0] || {}
})
})
},
beforeRouteUpdate ({ query, params }, from, next) {
api.post('/data/posts', {
cond: {
slug: params.pathMatch
},
limit: 1,
project: {
title: 1,
excerpt: 1,
remaining: 1,
date: 1,
tag: 1
}
}).then(({ data }) => {
this.$set(this, 'post', data.data[0] || {})
next()
})
},
metaInfo () {
const title = this.post.title
const description = (() => {
const div = document.createElement('div')
div.innerHTML = this.post.excerpt
return div.innerText
})()
return {
title,
meta: [
{ vmid: 'description', name: 'description', content: description },
{
hid: 'og:title',
property: 'og:title',
content: this.post.title
},
{
hid: 'og:description',
property: 'og:description',
content: description
},
{
hid: 'og:image',
property: 'og:image',
content: this.post.image
},
{
hid: 'twitter:title',
property: 'twitter:title',
content: this.post.title
},
{
hid: 'twitter:description',
property: 'twitter:description',
content: description
},
{
hid: 'twitter:image',
property: 'twitter:image',
content: this.post.image
},
{
hid: 'twitter:card',
property: 'twitter:card',
content: 'summary_large_image'
}
]
}
}
})
export default class PostView extends Vue {
post: any = {}
disqus = ''
}
</script>
<style lang="scss">
.image-full {
text-align: center;
margin: 1rem;
img {
min-width: 500px;
width: auto;
}
}
@media only screen and (max-width: 800px) {
.image-full {
margin-left: -1.5rem;
margin-right: -1.5rem;
img {
min-width: unset;
width: auto;
}
}
}
</style>
+66 -19
View File
@@ -1,13 +1,13 @@
<template lang="pug"> <template lang="pug">
section section
section.m-lg(v-if="tag") section(v-if="tag" style="margin: 1rem;")
h2.title.is-2 h2.title.is-2
span.mr-sm Tag: span(style="margin-right: 0.5rem;") Tag:
span(style="font-weight: 400") {{tag}} span(style="font-weight: 400") {{tag}}
section(v-if="posts.length > 0") section(v-if="posts.length > 0")
PostTeaser(v-for="p in posts" :header="p" :key="p.filename") PostTeaser(v-for="p in posts" :header="p" :key="p.slug")
b-pagination.my-lg.mx-sm(:current.sync="page" :total="count" :per-page="5" b-pagination(:current.sync="page" :total="count" :per-page="5"
size="default" rounded) size="default" rounded style="margin-top: 1rem;")
div(v-else) div(v-else)
Empty Empty
</template> </template>
@@ -16,17 +16,16 @@ section
import { Vue, Component } from 'vue-property-decorator' import { Vue, Component } from 'vue-property-decorator'
import PostTeaser from '../components/PostTeaser.vue' import PostTeaser from '../components/PostTeaser.vue'
import { api, normalizeArray } from '../utils' import Empty from '../components/Empty.vue'
import { api } from '../utils'
@Component<QueryView>({ @Component<QueryView>({
components: { components: {
PostTeaser PostTeaser,
Empty
}, },
beforeRouteEnter (to, from, next) { beforeRouteEnter ({ query, params }, from, next) {
console.log(to) api.post('/data/posts', {
const { query, params } = to
api.post('/data/', {
q: query.q, q: query.q,
cond: params.tag ? { cond: params.tag ? {
tag: params.tag tag: params.tag
@@ -35,7 +34,7 @@ import { api, normalizeArray } from '../utils'
limit: 5, limit: 5,
project: { project: {
title: 1, title: 1,
filename: 1, slug: 1,
excerpt: 1, excerpt: 1,
date: 1, date: 1,
tag: 1 tag: 1
@@ -48,7 +47,7 @@ import { api, normalizeArray } from '../utils'
}) })
}, },
beforeRouteUpdate ({ query, params }, from, next) { beforeRouteUpdate ({ query, params }, from, next) {
api.post('/data/', { api.post('/data/posts', {
q: query.q, q: query.q,
cond: params.tag ? { cond: params.tag ? {
tag: params.tag tag: params.tag
@@ -57,7 +56,7 @@ import { api, normalizeArray } from '../utils'
limit: 5, limit: 5,
project: { project: {
title: 1, title: 1,
filename: 1, slug: 1,
excerpt: 1, excerpt: 1,
date: 1, date: 1,
tag: 1 tag: 1
@@ -67,6 +66,58 @@ import { api, normalizeArray } from '../utils'
this.$set(this, 'count', data.count) this.$set(this, 'count', data.count)
next() 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 { export default class QueryView extends Vue {
@@ -101,9 +152,5 @@ export default class QueryView extends Vue {
}) })
} }
} }
get q () {
return normalizeArray(this.$route.query.q)
}
} }
</script> </script>