Skip to content

Commit 243fd99

Browse files
committed
feat: fast search
1 parent e515ffe commit 243fd99

4 files changed

Lines changed: 43 additions & 20 deletions

File tree

src/lib/forum.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getFile, putFile, listFiles } from './github';
2-
import { parseFrontmatter, stringifyFrontmatter, generateId, transliterate } from './utils';
2+
import { parseFrontmatter, stringifyFrontmatter, generateId, transliterate, normalizeText } from './utils';
33

44
export interface Forum {
55
slug: string;
@@ -28,6 +28,22 @@ export interface Post {
2828
body: string;
2929
}
3030

31+
function matchesSearch(item: { author: string; body: string }, options?: { author?: string; text?: string }): boolean {
32+
if (!options) return true;
33+
const { author: authorQuery, text: textQuery } = options;
34+
if (authorQuery) {
35+
const normalizedAuthor = normalizeText(item.author);
36+
const normalizedQuery = normalizeText(authorQuery);
37+
if (!normalizedAuthor.includes(normalizedQuery)) return false;
38+
}
39+
if (textQuery) {
40+
const normalizedBody = normalizeText(item.body);
41+
const normalizedQuery = normalizeText(textQuery);
42+
if (!normalizedBody.includes(normalizedQuery)) return false;
43+
}
44+
return true;
45+
}
46+
3147
export async function listForums(): Promise<Forum[]> {
3248
const files = await listFiles('forums');
3349
const forums: Forum[] = [];
@@ -66,7 +82,7 @@ export async function createForum(data: Omit<Forum, 'slug'> & {slug: string}): P
6682
};
6783
}
6884

69-
export async function listTopics(forumSlug: string): Promise<Topic[]> {
85+
export async function listTopics(forumSlug: string, options?: { author?: string; text?: string }): Promise<Topic[]> {
7086
const files = await listFiles('topics');
7187
const topics: Topic[] = [];
7288

@@ -76,11 +92,14 @@ export async function listTopics(forumSlug: string): Promise<Topic[]> {
7692
if (fileData) {
7793
const { data, content } = parseFrontmatter<Topic>(fileData.content);
7894
if (data.forumSlug === forumSlug) {
79-
topics.push({
95+
const topic = {
8096
...data,
8197
id: file.name.replace('.md', ''),
8298
body: content
83-
});
99+
};
100+
if (matchesSearch(topic, options)) {
101+
topics.push(topic);
102+
}
84103
}
85104
}
86105
}
@@ -123,7 +142,7 @@ export async function createTopic(data: Omit<Topic, 'id' | 'createdAt' | 'titleT
123142
};
124143
}
125144

126-
export async function listPosts(topicId: string): Promise<Post[]> {
145+
export async function listPosts(topicId: string, options?: { author?: string; text?: string }): Promise<Post[]> {
127146
const files = await listFiles('posts');
128147
const posts: Post[] = [];
129148

@@ -133,11 +152,14 @@ export async function listPosts(topicId: string): Promise<Post[]> {
133152
if (fileData) {
134153
const { data, content } = parseFrontmatter<Post>(fileData.content);
135154
if (data.topicId === topicId) {
136-
posts.push({
155+
const post = {
137156
...data,
138157
id: file.name.replace('.md', ''),
139158
body: content
140-
});
159+
};
160+
if (matchesSearch(post, options)) {
161+
posts.push(post);
162+
}
141163
}
142164
}
143165
}

src/lib/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,8 @@ export function transliterate(text: string): string {
5959
.replace(/[^a-z0-9-]/g, '')
6060
.replace(/-+/g, '-')
6161
.replace(/^-+|-+$/g, '');
62+
}
63+
64+
export function normalizeText(text: string): string {
65+
return transliterate(text).toLowerCase();
6266
}

src/pages/ForumPage.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export function ForumPage() {
3434
try {
3535
const [forumData, topicsData] = await Promise.all([
3636
getForum(slug),
37-
listTopics(slug)]
38-
);
37+
listTopics(slug, { author: authorSearch || undefined, text: textSearch || undefined })
38+
]);
3939
setForum(forumData);
4040
setTopics(topicsData);
4141
} catch (err: any) {
@@ -47,11 +47,7 @@ export function ForumPage() {
4747
};
4848
useEffect(() => {
4949
loadData();
50-
}, [slug]);
51-
const filteredTopics = topics.filter(topic =>
52-
(authorSearch === '' || topic.author.toLowerCase().includes(authorSearch.toLowerCase())) &&
53-
(textSearch === '' || topic.body.toLowerCase().includes(textSearch.toLowerCase()))
54-
);
50+
}, [slug, authorSearch, textSearch]);
5551
const handleCreateTopic = async (e: React.FormEvent) => {
5652
e.preventDefault();
5753
if (!user || !slug) return;
@@ -149,7 +145,7 @@ export function ForumPage() {
149145
</div> :
150146

151147
<div className="divide-y divide-gray-200 dark:divide-gray-700">
152-
{filteredTopics.map((topic) =>
148+
{topics.map((topic) =>
153149
<Link
154150
key={topic.id}
155151
to={`/topic/${topic.id}`}

src/pages/TopicPage.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useTranslation } from '../lib/i18n';
66
import { useAuth } from '../lib/auth';
77
import { MarkdownContent } from '../components/MarkdownContent';
88
import { MarkdownEditor } from '../components/MarkdownEditor';
9+
import { normalizeText } from '../lib/utils';
910
export function TopicPage() {
1011
const { id } = useParams<{
1112
id: string;
@@ -27,8 +28,8 @@ export function TopicPage() {
2728
try {
2829
const [topicData, postsData] = await Promise.all([
2930
getTopic(id),
30-
listPosts(id)]
31-
);
31+
listPosts(id, { author: authorSearch || undefined, text: textSearch || undefined })
32+
]);
3233
setTopic(topicData);
3334
setPosts(postsData);
3435
} catch (err: any) {
@@ -40,14 +41,14 @@ export function TopicPage() {
4041
};
4142
useEffect(() => {
4243
loadData();
43-
}, [id]);
44+
}, [id, authorSearch, textSearch]);
4445
const allMessages = topic ? [
4546
{ id: 'original', author: topic.author, body: topic.body, createdAt: topic.createdAt },
4647
...posts
4748
] : [];
4849
const filteredMessages = allMessages.filter(message =>
49-
(authorSearch === '' || message.author.toLowerCase().includes(authorSearch.toLowerCase())) &&
50-
(textSearch === '' || message.body.toLowerCase().includes(textSearch.toLowerCase()))
50+
(authorSearch === '' || normalizeText(message.author).includes(normalizeText(authorSearch))) &&
51+
(textSearch === '' || normalizeText(message.body).includes(normalizeText(textSearch)))
5152
);
5253
const handleReply = async (e: React.FormEvent) => {
5354
e.preventDefault();

0 commit comments

Comments
 (0)