add query page
This commit is contained in:
1 parent
c2ae07be06
commit
3bc2a943bd
31 files changed
+380
-164
No files matched your search
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@patarapolw/qsearch": "^1.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.8.21",
|
||||
"express": "^4.17.1",
|
||||
"fast-glob": "^3.2.2",
|
||||
"gray-matter": "^4.0.2",
|
||||
|
||||
@@ -4,6 +4,7 @@ import crypto from 'crypto'
|
||||
import matter from 'gray-matter'
|
||||
import glob from 'fast-glob'
|
||||
import DataStore from 'nedb-promises'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import MakeHtml from '../src/utils/make-html'
|
||||
|
||||
@@ -44,6 +45,7 @@ const db = new DataStore('assets/data.nedb')
|
||||
|
||||
await db.insert({
|
||||
...data,
|
||||
date: formatDate(data.date),
|
||||
filename: f,
|
||||
h,
|
||||
excerpt: computedExcerpt,
|
||||
@@ -64,8 +66,13 @@ 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,
|
||||
h,
|
||||
excerpt: computedExcerpt,
|
||||
@@ -77,3 +84,17 @@ const db = new DataStore('assets/data.nedb')
|
||||
}
|
||||
}))
|
||||
})().catch(console.error)
|
||||
|
||||
const tzOffset = new Date().getTimezoneOffset()
|
||||
|
||||
function formatDate (d?: string | Date) {
|
||||
if (d) {
|
||||
if (typeof d === 'string') {
|
||||
return dayjs(d).toDate()
|
||||
} else {
|
||||
return dayjs(d).add(tzOffset, 'minute').toDate()
|
||||
}
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
@@ -26,26 +26,29 @@ dataRouter.get('/*', (req, res) => {
|
||||
|
||||
dataRouter.post('/', async (req, res, next) => {
|
||||
try {
|
||||
const { q, project, offset, limit } = req.body
|
||||
let cond = q
|
||||
if (typeof q === 'string') {
|
||||
const { q, cond = {}, project, offset, limit } = req.body
|
||||
const $and = [
|
||||
{ filename: { $regex: /^posts\// } },
|
||||
cond
|
||||
] as any[]
|
||||
|
||||
if (q) {
|
||||
const parser = new QSearch({
|
||||
dialect: 'nedb',
|
||||
schema: {
|
||||
title: {},
|
||||
filename: {},
|
||||
excerpt: {},
|
||||
date: { type: 'date' }
|
||||
}
|
||||
})
|
||||
cond = parser.parse(q)
|
||||
$and.push(parser.parse(q))
|
||||
}
|
||||
|
||||
const count = await db.count(cond)
|
||||
const count = await db.count({ $and })
|
||||
|
||||
let c = db.find({ $and }, project).skip(offset || 0)
|
||||
|
||||
let c = db.find(cond).skip(offset || 0)
|
||||
if (project) {
|
||||
c = c.projection(project)
|
||||
}
|
||||
if (limit !== null) {
|
||||
c = c.limit(limit || 5)
|
||||
}
|
||||
|
||||
@@ -420,6 +420,11 @@ dateformat@~1.0.4-1.2.3:
|
||||
get-stdin "^4.0.1"
|
||||
meow "^3.3.0"
|
||||
|
||||
dayjs@^1.8.21:
|
||||
version "1.8.21"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.21.tgz#98299185b72b9b679f31c7ed987b63923c961552"
|
||||
integrity sha512-1kbWK0hziklUHkGgiKr7xm59KwAg/K3Tp7H/8X+f58DnNCwY3pKYjOCJpIlVs125FRBukGVZdKZojC073D0IeQ==
|
||||
|
||||
debounce@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"axios": "^0.19.2",
|
||||
"buefy": "^0.8.12",
|
||||
"core-js": "^3.6.4",
|
||||
"dayjs": "^1.8.21",
|
||||
"vue": "^2.6.11",
|
||||
"vue-class-component": "^7.2.2",
|
||||
"vue-extend-layout": "^2.0.6",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="/api/favicon.ico">
|
||||
<link rel="stylesheet" href="https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css">
|
||||
<title>ZhDiary</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -10,4 +10,10 @@ export default {
|
||||
name: 'App',
|
||||
components: { VueExtendLayouts }
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.d-inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
Whitespace-only changes.
Binary file not shown.
|
Before Width: | Height: | Size: 6.7 KiB |
@@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
@Component
|
||||
export default class HelloWorld extends Vue {
|
||||
@Prop() private msg!: string;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template lang="pug">
|
||||
.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}}
|
||||
div(style="flex-grow: 1")
|
||||
div {{dateString}}
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop } from 'vue-property-decorator'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { config } from '../global'
|
||||
|
||||
@Component
|
||||
export default class PostHeader extends Vue {
|
||||
@Prop({ required: true }) header!: any
|
||||
|
||||
author = config.author || {}
|
||||
|
||||
get dateString () {
|
||||
return this.header.date ? dayjs(this.header.date).format("ddd D MMMM YYYY") : ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template lang="pug">
|
||||
.card.my-lg.blog-post
|
||||
.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")
|
||||
div(style="display: flex; justify-content: flex-end")
|
||||
router-link.button.is-danger.is-outlined(:to="url") Read more
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop } from 'vue-property-decorator'
|
||||
|
||||
import PostHeader from './PostHeader.vue'
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PostHeader
|
||||
}
|
||||
})
|
||||
export default class PostTeaser extends Vue {
|
||||
@Prop({ required: true }) header!: any
|
||||
|
||||
get url () {
|
||||
return this.header.filename.replace(/\.md$/, '')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.content {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
max-width: 80vw;
|
||||
}
|
||||
.post-content {
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
.image-teaser {
|
||||
width: calc(100% + 3rem);
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-left: -1.5rem;
|
||||
margin-right: -1.5rem;
|
||||
}
|
||||
@media only screen and (min-width: 800px) {
|
||||
.image-teaser {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
max-height: 300px;
|
||||
float: right;
|
||||
margin: 1rem;
|
||||
}
|
||||
.post-content {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -6,7 +6,9 @@ section
|
||||
b-navbar-item(tag="router-link" to="/")
|
||||
h1.bold {{config.banner}}
|
||||
template(slot="start")
|
||||
b-navbar-item(v-for="t in config.tabs"
|
||||
b-navbar-item(
|
||||
v-for="t in config.tabs"
|
||||
:key="t.name"
|
||||
:tag="t.to ? 'router-link' : 'a'"
|
||||
:href="t.to ? '' : t.url"
|
||||
:to="t.to ? t.to : ''"
|
||||
@@ -20,7 +22,7 @@ section
|
||||
fa(icon="search")
|
||||
section(style="margin-top: 3.25rem")
|
||||
.columns
|
||||
.column.column.is-10-desktop.is-offset-1-desktop
|
||||
.column.column.is-three-fifths-desktop.is-offset-one-fifth-desktop
|
||||
router-view
|
||||
</template>
|
||||
|
||||
@@ -44,7 +46,7 @@ import { normalizeArray } from '../utils'
|
||||
{
|
||||
hid: 'og:url',
|
||||
property: 'og:url',
|
||||
content: this.fullUrl
|
||||
content: url
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,10 +6,33 @@ Vue.use(VueRouter)
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('../views/QueryView.vue'),
|
||||
meta: {
|
||||
layout: 'default'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/:page',
|
||||
alias: '/',
|
||||
component: () => import('../views/Home.vue')
|
||||
component: () => import('../views/QueryView.vue'),
|
||||
meta: {
|
||||
layout: 'default'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/tag/:tag',
|
||||
component: () => import('../views/QueryView.vue'),
|
||||
meta: {
|
||||
layout: 'default'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/tag/:tag/:page',
|
||||
component: () => import('../views/QueryView.vue'),
|
||||
meta: {
|
||||
layout: 'default'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import axios from 'axios'
|
||||
|
||||
export function normalizeArray<T> (a: T | T[]): T | undefined {
|
||||
return Array.isArray(a) ? a[0] : a
|
||||
}
|
||||
|
||||
export const isRendered = !!document.getElementById('app')!.getAttribute('data-server-rendered')
|
||||
|
||||
export const api = axios.create({
|
||||
baseURL: isRendered ? '/api' : 'http://localhost:48000/api'
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<img alt="Vue logo" src="../assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import HelloWorld from '@/components/HelloWorld.vue'
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
HelloWorld
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template lang="pug">
|
||||
section
|
||||
section.m-lg(v-if="tag")
|
||||
h2.title.is-2
|
||||
span.mr-sm 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)
|
||||
div(v-else)
|
||||
Empty
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component } from 'vue-property-decorator'
|
||||
|
||||
import PostTeaser from '../components/PostTeaser.vue'
|
||||
import { api, normalizeArray } from '../utils'
|
||||
|
||||
@Component<QueryView>({
|
||||
components: {
|
||||
PostTeaser
|
||||
},
|
||||
beforeRouteEnter (to, from, next) {
|
||||
console.log(to)
|
||||
const { query, params } = to
|
||||
|
||||
api.post('/data/', {
|
||||
q: query.q,
|
||||
cond: params.tag ? {
|
||||
tag: params.tag
|
||||
} : undefined,
|
||||
offset: (parseInt(params.page || '1') - 1) * 5,
|
||||
limit: 5,
|
||||
project: {
|
||||
title: 1,
|
||||
filename: 1,
|
||||
excerpt: 1,
|
||||
date: 1,
|
||||
tag: 1
|
||||
}
|
||||
}).then(({ data }) => {
|
||||
next((vm) => {
|
||||
vm.posts = data.data
|
||||
vm.count = data.count
|
||||
})
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate ({ query, params }, from, next) {
|
||||
api.post('/data/', {
|
||||
q: query.q,
|
||||
cond: params.tag ? {
|
||||
tag: params.tag
|
||||
} : undefined,
|
||||
offset: (parseInt(params.page || '1') - 1) * 5,
|
||||
limit: 5,
|
||||
project: {
|
||||
title: 1,
|
||||
filename: 1,
|
||||
excerpt: 1,
|
||||
date: 1,
|
||||
tag: 1
|
||||
}
|
||||
}).then(({ data }) => {
|
||||
this.$set(this, 'posts', data.data)
|
||||
this.$set(this, 'count', data.count)
|
||||
next()
|
||||
})
|
||||
}
|
||||
})
|
||||
export default class QueryView extends Vue {
|
||||
posts: any[] = []
|
||||
count: number = 0
|
||||
|
||||
get tag () {
|
||||
return this.$route.params.tag
|
||||
}
|
||||
|
||||
get page () {
|
||||
return parseInt(this.$route.params.page || '1')
|
||||
}
|
||||
|
||||
set page (page: number) {
|
||||
const { tag } = this.$route.params
|
||||
const { q } = this.$route.query
|
||||
|
||||
if (page > 1) {
|
||||
this.$router.push({
|
||||
path: (q || !tag) ? `/${page}` : `/tag/${tag}/${page}`,
|
||||
query: {
|
||||
q
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$router.push({
|
||||
path: (q || !tag) ? '/' : `/tag/${tag}`,
|
||||
query: {
|
||||
q
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
get q () {
|
||||
return normalizeArray(this.$route.query.q)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2794,6 +2794,11 @@ dashdash@^1.12.0:
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
dayjs@^1.8.21:
|
||||
version "1.8.21"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.21.tgz#98299185b72b9b679f31c7ed987b63923c961552"
|
||||
integrity sha512-1kbWK0hziklUHkGgiKr7xm59KwAg/K3Tp7H/8X+f58DnNCwY3pKYjOCJpIlVs125FRBukGVZdKZojC073D0IeQ==
|
||||
|
||||
de-indent@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||
|
||||
Reference in new issue
Block a user