feat: add pagination for search result.

This commit is contained in:
Yufan Sheng 2024-07-07 21:13:55 +08:00
parent d3f76c2c85
commit c7a4443513
Signed by: syhily
GPG Key ID: 9D18A22A7DCD5A9B
13 changed files with 548 additions and 491 deletions

883
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -44,12 +44,12 @@
"@astrojs/mdx": "^3.1.2", "@astrojs/mdx": "^3.1.2",
"@astrojs/node": "^8.3.2", "@astrojs/node": "^8.3.2",
"@astrojs/rss": "^4.0.7", "@astrojs/rss": "^4.0.7",
"astro": "^4.11.3", "astro": "^4.11.5",
"drizzle-orm": "^0.31.2", "drizzle-orm": "^0.31.2",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"marked": "^13.0.1", "marked": "^13.0.2",
"pg": "^8.12.0", "pg": "^8.12.0",
"qrcode-svg": "^1.1.0", "qrcode-svg": "^1.1.0",
"ultrahtml": "^1.5.3" "ultrahtml": "^1.5.3"
@ -60,7 +60,7 @@
"@napi-rs/canvas": "^0.1.53", "@napi-rs/canvas": "^0.1.53",
"@types/lodash": "^4.17.6", "@types/lodash": "^4.17.6",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^20.14.9", "@types/node": "^20.14.10",
"@types/pg": "^8.11.6", "@types/pg": "^8.11.6",
"@types/qrcode-svg": "^1.1.4", "@types/qrcode-svg": "^1.1.4",
"@types/unist": "^3.0.2", "@types/unist": "^3.0.2",
@ -73,7 +73,7 @@
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"rehype-external-links": "^3.0.0", "rehype-external-links": "^3.0.0",
"resize-sensor": "^0.0.6", "resize-sensor": "^0.0.6",
"rimraf": "^5.0.7", "rimraf": "^5.0.8",
"sharp": "^0.33.4", "sharp": "^0.33.4",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"unist-util-select": "^5.1.0" "unist-util-select": "^5.1.0"

View File

@ -91,7 +91,7 @@ if (typeof searchSidebar !== 'undefined' && searchSidebar !== null) {
const query = event.target.value; const query = event.target.value;
event.target.value = ''; event.target.value = '';
location.href = `/search?q=${encodeURIComponent(query)}`; location.href = `/search/${encodeURIComponent(query)}`;
} }
}); });
} }
@ -108,6 +108,15 @@ document.querySelector('.global-search-close').addEventListener('click', (event)
searchPopup.classList.toggle('nice-popup-open', false); searchPopup.classList.toggle('nice-popup-open', false);
}); });
searchPopup.querySelector('.search-dialog').addEventListener('submit', (event) => {
event.preventDefault();
event.stopPropagation();
const formData = new FormData(event.target);
const query = formData.get('q');
location.href = `/search/${encodeURIComponent(query)}`;
});
// Loading the comments. // Loading the comments.
const comments = document.querySelector('#comments'); const comments = document.querySelector('#comments');
if (typeof comments !== 'undefined' && comments !== null) { if (typeof comments !== 'undefined' && comments !== null) {

View File

@ -2,7 +2,7 @@
import Logo from '@/components/header/Logo.astro'; import Logo from '@/components/header/Logo.astro';
import LogoLarge from '@/components/header/LogoLarge.astro'; import LogoLarge from '@/components/header/LogoLarge.astro';
import QRDialog from '@/components/image/QRDialog.astro'; import QRDialog from '@/components/image/QRDialog.astro';
import SearchDialog from '@/components/search/SearchDialog.astro'; import SearchIcon from '@/components/search/SearchIcon.astro';
import options from '@/options'; import options from '@/options';
--- ---
@ -50,7 +50,7 @@ import options from '@/options';
} }
}) })
} }
<SearchDialog /> <SearchIcon />
</div> </div>
</div> </div>
</header> </header>

View File

@ -1,13 +1,3 @@
---
---
<div class="global-search btn btn-dark btn-icon btn-circle site-search-toggler button-social">
<span>
<i class="iconfont icon-search"></i>
</span>
</div>
<div class="global-search-popup nice-popup nice-popup-center nice-popup-md"> <div class="global-search-popup nice-popup nice-popup-center nice-popup-md">
<div class="nice-popup-overlay"></div> <div class="nice-popup-overlay"></div>
<div class="nice-popup-body"> <div class="nice-popup-body">
@ -16,7 +6,7 @@
<span class="svg-dark"></span> <span class="svg-dark"></span>
</div> </div>
<div class="nice-popup-content"> <div class="nice-popup-content">
<form class="text-center p-3 p-md-5" action={'/search'}> <form class="search-dialog text-center p-3 p-md-5" action="/search">
<div class="mb-3 mb-md-4"> <div class="mb-3 mb-md-4">
<input <input
class="form-control form-control-lg text-center" class="form-control form-control-lg text-center"

View File

@ -0,0 +1,5 @@
<div class="global-search btn btn-dark btn-icon btn-circle site-search-toggler button-social">
<span>
<i class="iconfont icon-search"></i>
</span>
</div>

View File

@ -10,6 +10,7 @@ import '@/assets/styles/opposans.css';
import Footer from '@/components/footer/Footer.astro'; import Footer from '@/components/footer/Footer.astro';
import Header from '@/components/header/Header.astro'; import Header from '@/components/header/Header.astro';
import PageMeta from '@/components/meta/PageMeta.astro'; import PageMeta from '@/components/meta/PageMeta.astro';
import SearchDialog from '@/components/search/SearchDialog.astro';
import options from '@/options'; import options from '@/options';
interface Props { interface Props {
@ -67,5 +68,6 @@ const description = Astro.props.description || options.description;
<script> <script>
import '../assets/scripts/yufan.me.js'; import '../assets/scripts/yufan.me.js';
</script> </script>
<SearchDialog />
</body> </body>
</html> </html>

View File

@ -4,8 +4,7 @@ import PostCards from '@/components/page/post/PostCards.astro';
import PinnedCategories from '@/components/page/post/PostCategory.astro'; import PinnedCategories from '@/components/page/post/PostCategory.astro';
import Sidebar from '@/components/sidebar/Sidebar.astro'; import Sidebar from '@/components/sidebar/Sidebar.astro';
import type { Post, Tag } from '@/helpers/schema'; import type { Post, Tag } from '@/helpers/schema';
import BaseLayout from '@/layouts/BaseLayout.astro';
import BaseLayout from '../BaseLayout.astro';
interface Props { interface Props {
posts: Post[]; posts: Post[];

View File

@ -1,14 +1,31 @@
--- ---
import Pagination from '@/components/page/pagination/Pagination.astro';
import PostSquare from '@/components/page/post/PostSquare.astro'; import PostSquare from '@/components/page/post/PostSquare.astro';
import { slicePosts } from '@/helpers/formatter';
import type { Post } from '@/helpers/schema'; import type { Post } from '@/helpers/schema';
import BaseLayout from '@/layouts/BaseLayout.astro'; import BaseLayout from '@/layouts/BaseLayout.astro';
import options from '@/options';
interface Props { interface Props {
title: string; title: string;
query: string;
posts: Post[]; posts: Post[];
pageNum: number;
} }
const { title, posts } = Astro.props; const { title, posts, query, pageNum } = Astro.props;
// Redirect to the homepage.
if (pageNum < 1) {
return Astro.redirect('/');
}
const results = slicePosts(posts, pageNum, options.settings.pagination.search);
if (!results) {
return Astro.redirect('/404');
}
const { currentPosts, totalPage } = results;
--- ---
<BaseLayout {title}> <BaseLayout {title}>
@ -18,7 +35,7 @@ const { title, posts } = Astro.props;
<h1>{title}</h1> <h1>{title}</h1>
</div> </div>
{ {
posts.length === 0 ? ( currentPosts.length === 0 ? (
<div class="data-null"> <div class="data-null">
<div class="my-auto"> <div class="my-auto">
<h1 class="font-number">404</h1> <h1 class="font-number">404</h1>
@ -26,11 +43,18 @@ const { title, posts } = Astro.props;
</div> </div>
</div> </div>
) : ( ) : (
<>
<div class="row g-2 g-md-3 g-xxl-4 list-grouped"> <div class="row g-2 g-md-3 g-xxl-4 list-grouped">
{posts.map((post, index) => ( {currentPosts.map((post, index) => (
<PostSquare post={post} first={index === 0} /> <PostSquare post={post} first={index === 0} />
))} ))}
</div> </div>
{totalPage > 1 && (
<div class="mt-4 mt-lg-5">
<Pagination current={pageNum} total={totalPage} rootPath={`/search/${query}`} />
</div>
)}
</>
) )
} }
</div> </div>

View File

@ -0,0 +1,19 @@
---
import { posts } from '@/helpers/schema';
import { searchPosts } from '@/helpers/search';
import SearchLayout from '@/layouts/posts/SearchLayout.astro';
const { keyword } = Astro.params;
const query = keyword || '';
if (query === '') {
return Astro.redirect('/');
}
const title = `【${query}】搜索结果`;
const searchResults = searchPosts(query)
.map((slug) => posts.find((post) => post.slug === slug))
.filter((post) => post !== undefined);
---
<SearchLayout {title} {query} pageNum={1} posts={searchResults} />

View File

@ -0,0 +1,32 @@
---
import { posts } from '@/helpers/schema';
import { searchPosts } from '@/helpers/search';
import SearchLayout from '@/layouts/posts/SearchLayout.astro';
import options from '@/options';
const { keyword, num } = Astro.params;
const query = keyword || '';
if (query === '' || !num) {
return Astro.redirect('/');
}
const pageNum = Number.parseInt(num);
if (pageNum <= 1) {
return Astro.redirect('/');
}
const title = `【${query}】搜索结果`;
const searchResults = searchPosts(query)
.map((slug) => posts.find((post) => post.slug === slug))
.filter((post) => post !== undefined);
const pageSize = options.settings.pagination.tags;
const total = Math.ceil(searchResults.length / pageSize);
if (pageNum > total) {
return Astro.redirect('/404');
}
---
<SearchLayout {title} {query} {pageNum} posts={searchResults} />

View File

@ -0,0 +1,3 @@
---
return Astro.redirect('/');
---

View File

@ -1,20 +1,3 @@
--- ---
import { posts } from '@/helpers/schema'; return Astro.redirect('/');
import { searchPosts } from '@/helpers/search';
import SearchLayout from '@/layouts/posts/SearchLayout.astro';
import options from '@/options';
const query = Astro.url.searchParams.get('q') || '';
if (query === '') {
return Astro.redirect('/');
}
const title = `${query} 查询结果`;
const searchResults = searchPosts(query)
.map((slug) => posts.find((post) => post.slug === slug))
.filter((post) => post !== undefined)
.slice(0, options.settings.pagination.search);
--- ---
<SearchLayout {title} posts={searchResults} />