From e3fbe853df3f39f4dc509a3d2875d99f70d2a153 Mon Sep 17 00:00:00 2001 From: Pacharapol Withayasakpunt Date: Fri, 28 Feb 2020 13:03:53 +0700 Subject: [PATCH] basic ui --- .eslintignore | 1 + data/config.json | 7 +- web/backend/scripts/prepare.ts | 15 ++- web/backend/src/api/data.ts | 12 +- web/backend/src/utils/make-html.ts | 2 + web/frontend/src/components/Empty.vue | 11 ++ web/frontend/src/components/PostHeader.vue | 13 +- web/frontend/src/components/PostTeaser.vue | 10 +- web/frontend/src/layouts/default.vue | 22 +-- web/frontend/src/router/index.ts | 7 + web/frontend/src/views/PostView.vue | 148 +++++++++++++++++++++ web/frontend/src/views/QueryView.vue | 85 +++++++++--- 12 files changed, 280 insertions(+), 53 deletions(-) create mode 100644 web/frontend/src/components/Empty.vue create mode 100644 web/frontend/src/views/PostView.vue 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 @@ + + + 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 @@ @@ -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 @@ @@ -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 @@ + + + + + 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 @@ @@ -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) - } }