fix searching
This commit is contained in:
1 parent
25585aa3da
commit
43edfb60d4
10 files changed
+126
-102
No files matched your search
@@ -1,8 +0,0 @@
|
|||||||
export function formatDate (s: string | number) {
|
|
||||||
return new Date(s).toLocaleDateString([], {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
weekday: 'short'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
import { THEME } from '~/assets/global'
|
import { THEME } from '~/assets/global'
|
||||||
|
import styles from '~/styles/Layout.module.css'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { ReactNode } from 'react'
|
import { useRouter } from 'next/router'
|
||||||
|
import { ReactNode, useState } from 'react'
|
||||||
|
|
||||||
|
const Layout = ({ children }: { children: ReactNode | ReactNode[] }) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const [q, setQ] = useState(router.query.q as string)
|
||||||
|
|
||||||
const Layout = ({ children }: { children: ReactNode }) => {
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<header>
|
<header className={styles.Header}>
|
||||||
<h1>{THEME.title}</h1>
|
<h1>{THEME.title}</h1>
|
||||||
|
|
||||||
<nav>
|
<nav className={styles.Nav}>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
@@ -34,35 +39,28 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<form>
|
<form
|
||||||
<input name="q" type="search" placeholder="Search..." />
|
className={styles.Form}
|
||||||
<div className="icon">🔎</div>
|
onSubmit={(ev) => {
|
||||||
|
if (router.pathname.startsWith('/post/')) {
|
||||||
|
ev.preventDefault()
|
||||||
|
router.push(`/blog?q=${encodeURIComponent(q)}`)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
name="q"
|
||||||
|
type="search"
|
||||||
|
placeholder="Search..."
|
||||||
|
value={q}
|
||||||
|
onInput={(ev) => setQ((ev.target as HTMLInputElement).value)}
|
||||||
|
/>
|
||||||
|
<div className={styles.icon}>🔎</div>
|
||||||
</form>
|
</form>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<style jsx>{`
|
|
||||||
nav ul {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
float: right;
|
|
||||||
max-width: 300px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .icon {
|
|
||||||
position: absolute;
|
|
||||||
right: 5px;
|
|
||||||
top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
padding-right: 1.5em;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ const PostQuery = ({
|
|||||||
return pathname
|
return pathname
|
||||||
}
|
}
|
||||||
|
|
||||||
const [posts, setPosts] = useState(defaults.posts)
|
const [posts, setPosts] = useState([] as IPost[])
|
||||||
const [count, setCount] = useState(defaults.count)
|
const [count, setCount] = useState(0)
|
||||||
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.query.q) {
|
if (router.query.q) {
|
||||||
@@ -62,13 +63,15 @@ const PostQuery = ({
|
|||||||
setPosts(defaults.posts)
|
setPosts(defaults.posts)
|
||||||
setCount(defaults.count)
|
setCount(defaults.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLoading(false)
|
||||||
}, [router.query.q, router.query.page, router.query.tag])
|
}, [router.query.q, router.query.page, router.query.tag])
|
||||||
|
|
||||||
const totalPages = Math.ceil(count / 5)
|
const totalPages = Math.ceil(count / 5)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article>
|
<article>
|
||||||
{posts.length ? (
|
{isLoading ? null : posts.length ? (
|
||||||
posts.map((p) => <PostTeaser post={p} key={p.path} />)
|
posts.map((p) => <PostTeaser post={p} key={p.path} />)
|
||||||
) : (
|
) : (
|
||||||
<p>No posts found.</p>
|
<p>No posts found.</p>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { THEME } from '~/assets/global'
|
|
||||||
import { formatDate } from '~/assets/util'
|
|
||||||
import { IPost } from '~/server/db'
|
import { IPost } from '~/server/db'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import TitleDescription from './TitleDesciption'
|
||||||
|
|
||||||
const PostTeaser = ({ post }: { post: IPost }) => {
|
const PostTeaser = ({ post }: { post: IPost }) => {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
@@ -12,31 +12,8 @@ const PostTeaser = ({ post }: { post: IPost }) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<small>
|
<TitleDescription date={post.date} />
|
||||||
<span>
|
<article dangerouslySetInnerHTML={{ __html: post.excerptHtml }}></article>
|
||||||
by
|
|
||||||
{THEME.author.url ? (
|
|
||||||
<a
|
|
||||||
href={THEME.author.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{THEME.author.name}
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<span>{THEME.author.name}</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{post.date ? (
|
|
||||||
<span style={{ float: 'right' }}>{formatDate(post.date)}</span>
|
|
||||||
) : null}
|
|
||||||
</small>
|
|
||||||
|
|
||||||
<article
|
|
||||||
style={{ marginTop: '2rem' }}
|
|
||||||
dangerouslySetInnerHTML={{ __html: post.excerptHtml }}
|
|
||||||
></article>
|
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { THEME } from '~/assets/global'
|
||||||
|
import styles from '~/styles/TitleDescription.module.css'
|
||||||
|
|
||||||
|
const TitleDescription = ({ date }: { date?: number }) => {
|
||||||
|
return (
|
||||||
|
<small className={styles.Small}>
|
||||||
|
<span>
|
||||||
|
by
|
||||||
|
{THEME.author.url ? (
|
||||||
|
<a href={THEME.author.url} target="_blank" rel="noopener noreferrer">
|
||||||
|
{THEME.author.name}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span>{THEME.author.name}</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{date ? (
|
||||||
|
<span className={styles.date}>
|
||||||
|
{new Date(date).toLocaleDateString([], {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
weekday: 'short'
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</small>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TitleDescription
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import '~/assets/remark42'
|
import '~/assets/remark42'
|
||||||
|
|
||||||
import { THEME } from '~/assets/global'
|
import { THEME } from '~/assets/global'
|
||||||
import { formatDate } from '~/assets/util'
|
import TitleDescription from '~/components/TitleDesciption'
|
||||||
import { IPost, dbPost, initDatabase } from '~/server/db'
|
import { IPost, dbPost, initDatabase } from '~/server/db'
|
||||||
import styles from '~/styles/PostPage.module.css'
|
import styles from '~/styles/PostPage.module.css'
|
||||||
import { GetStaticPaths, GetStaticProps } from 'next'
|
import { GetStaticPaths, GetStaticProps } from 'next'
|
||||||
@@ -18,11 +18,21 @@ const PostPage = (post: IPost) => {
|
|||||||
window.remark42Instance.destroy()
|
window.remark42Instance.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
window.remark42Instance = window.REMARK42.createInstance({
|
const initRemark42 = () => {
|
||||||
node: remark42Ref.current,
|
window.remark42Instance = window.REMARK42.createInstance({
|
||||||
host: THEME.comment.remark42.host,
|
node: remark42Ref.current,
|
||||||
site_id: THEME.comment.remark42.siteId
|
host: THEME.comment.remark42.host,
|
||||||
})
|
site_id: THEME.comment.remark42.siteId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.REMARK42) {
|
||||||
|
initRemark42()
|
||||||
|
} else {
|
||||||
|
window.addEventListener('REMARK42::ready', () => {
|
||||||
|
initRemark42()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [remark42Ref.current])
|
}, [remark42Ref.current])
|
||||||
|
|
||||||
@@ -53,26 +63,7 @@ const PostPage = (post: IPost) => {
|
|||||||
<section>
|
<section>
|
||||||
<h2>{post.title}</h2>
|
<h2>{post.title}</h2>
|
||||||
|
|
||||||
<small>
|
<TitleDescription date={post.date} />
|
||||||
<span>
|
|
||||||
by{' '}
|
|
||||||
{THEME.author.url ? (
|
|
||||||
<a
|
|
||||||
href={THEME.author.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{THEME.author.name}
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<span>{THEME.author.name}</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{post.date ? (
|
|
||||||
<span style={{ float: 'right' }}>{formatDate(post.date)}</span>
|
|
||||||
) : null}
|
|
||||||
</small>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section dangerouslySetInnerHTML={{ __html: post.contentHtml }} />
|
<section dangerouslySetInnerHTML={{ __html: post.contentHtml }} />
|
||||||
@@ -105,11 +96,9 @@ export const getStaticPaths: GetStaticPaths = async () => {
|
|||||||
await initDatabase()
|
await initDatabase()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
paths: dbPost
|
paths: dbPost.find().map(({ path }) => ({
|
||||||
.find()
|
params: { path: path.split(/\//g).map(encodeURIComponent) }
|
||||||
.map(({ path }) => ({
|
})),
|
||||||
params: { path: path.split(/\//g).map(encodeURIComponent) }
|
|
||||||
})),
|
|
||||||
fallback: false
|
fallback: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ export let db: Loki
|
|||||||
|
|
||||||
export let dbPost: Collection<IPost>
|
export let dbPost: Collection<IPost>
|
||||||
|
|
||||||
export let idx: lunr.Index
|
let idx: lunr.Index
|
||||||
|
|
||||||
export async function initLunr () {
|
export async function getLunr () {
|
||||||
return idx || lunr.Index.load(require('../build/idx.json'))
|
return idx || lunr.Index.load(require('../build/idx.json'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { dbPost, idx, initDatabase, initLunr } from './db'
|
import { dbPost, getLunr, initDatabase } from './db'
|
||||||
|
|
||||||
export async function doSearch ({
|
export async function doSearch ({
|
||||||
q,
|
q,
|
||||||
@@ -10,7 +10,7 @@ export async function doSearch ({
|
|||||||
page?: number
|
page?: number
|
||||||
}) {
|
}) {
|
||||||
await initDatabase()
|
await initDatabase()
|
||||||
await initLunr()
|
const idx = await getLunr()
|
||||||
|
|
||||||
const perPage = 5
|
const perPage = 5
|
||||||
const offset = (page - 1) * perPage
|
const offset = (page - 1) * perPage
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.Header {
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Nav ul {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Form {
|
||||||
|
float: right;
|
||||||
|
max-width: 300px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Form input {
|
||||||
|
padding-right: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Form .icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.Small {
|
||||||
|
display: block;
|
||||||
|
min-height: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Small .date {
|
||||||
|
float: right;
|
||||||
|
min-width: 15em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user