Skip to content

Commit a5cb225

Browse files
committed
fix: debounce
1 parent 243fd99 commit a5cb225

4 files changed

Lines changed: 71 additions & 10 deletions

File tree

src/lib/forum.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ function matchesSearch(item: { author: string; body: string }, options?: { autho
3939
if (textQuery) {
4040
const normalizedBody = normalizeText(item.body);
4141
const normalizedQuery = normalizeText(textQuery);
42-
if (!normalizedBody.includes(normalizedQuery)) return false;
42+
const words = normalizedBody.split(/\s+/);
43+
if (!words.some(word => word.startsWith(normalizedQuery))) return false;
4344
}
4445
return true;
4546
}

src/lib/utils.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,22 @@ export function transliterate(text: string): string {
6262
}
6363

6464
export function normalizeText(text: string): string {
65-
return transliterate(text).toLowerCase();
65+
const russianToLatin: Record<string, string> = {
66+
'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'Yo',
67+
'Ж': 'Zh', 'З': 'Z', 'И': 'I', 'Й': 'Y', 'К': 'K', 'Л': 'L', 'М': 'M',
68+
'Н': 'N', 'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U',
69+
'Ф': 'F', 'Х': 'Kh', 'Ц': 'Ts', 'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Sch',
70+
'Ъ': '', 'Ы': 'Y', 'Ь': '', 'Э': 'E', 'Ю': 'Yu', 'Я': 'Ya',
71+
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo',
72+
'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm',
73+
'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
74+
'ф': 'f', 'х': 'kh', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'sch',
75+
'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya'
76+
};
77+
return text
78+
.split('')
79+
.map(char => russianToLatin[char] || char)
80+
.join('')
81+
.toLowerCase()
82+
.trim();
6683
}

src/pages/ForumPage.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useEffect, useState, useRef } from 'react';
22
import { useParams, Link } from 'react-router-dom';
33
import {
44
Plus,
@@ -25,16 +25,35 @@ export function ForumPage() {
2525
const [newTopicBody, setNewTopicBody] = useState('');
2626
const [authorSearch, setAuthorSearch] = useState('');
2727
const [textSearch, setTextSearch] = useState('');
28+
const [debouncedAuthorSearch, setDebouncedAuthorSearch] = useState('');
29+
const [debouncedTextSearch, setDebouncedTextSearch] = useState('');
30+
const authorTimeoutRef = useRef<NodeJS.Timeout | null>(null);
31+
const textTimeoutRef = useRef<NodeJS.Timeout | null>(null);
2832
const { t } = useTranslation();
2933
const { user } = useAuth();
34+
35+
useEffect(() => {
36+
if (authorTimeoutRef.current) clearTimeout(authorTimeoutRef.current);
37+
authorTimeoutRef.current = setTimeout(() => {
38+
setDebouncedAuthorSearch(authorSearch);
39+
}, 300);
40+
}, [authorSearch]);
41+
42+
useEffect(() => {
43+
if (textTimeoutRef.current) clearTimeout(textTimeoutRef.current);
44+
textTimeoutRef.current = setTimeout(() => {
45+
setDebouncedTextSearch(textSearch);
46+
}, 300);
47+
}, [textSearch]);
48+
3049
const loadData = async () => {
3150
if (!slug) return;
3251
setIsLoading(true);
3352
setError('');
3453
try {
3554
const [forumData, topicsData] = await Promise.all([
3655
getForum(slug),
37-
listTopics(slug, { author: authorSearch || undefined, text: textSearch || undefined })
56+
listTopics(slug, { author: debouncedAuthorSearch || undefined, text: debouncedTextSearch || undefined })
3857
]);
3958
setForum(forumData);
4059
setTopics(topicsData);
@@ -47,7 +66,7 @@ export function ForumPage() {
4766
};
4867
useEffect(() => {
4968
loadData();
50-
}, [slug, authorSearch, textSearch]);
69+
}, [slug, debouncedAuthorSearch, debouncedTextSearch]);
5170
const handleCreateTopic = async (e: React.FormEvent) => {
5271
e.preventDefault();
5372
if (!user || !slug) return;

src/pages/TopicPage.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useEffect, useState, useRef } from 'react';
22
import { useParams, Link } from 'react-router-dom';
33
import { Loader2, User as UserIcon, Clock, Send, Quote } from 'lucide-react';
44
import { Topic, Post, getTopic, listPosts, createPost } from '../lib/forum';
@@ -19,16 +19,35 @@ export function TopicPage() {
1919
const [isReplying, setIsReplying] = useState(false);
2020
const [authorSearch, setAuthorSearch] = useState('');
2121
const [textSearch, setTextSearch] = useState('');
22+
const [debouncedAuthorSearch, setDebouncedAuthorSearch] = useState('');
23+
const [debouncedTextSearch, setDebouncedTextSearch] = useState('');
24+
const authorTimeoutRef = useRef<NodeJS.Timeout | null>(null);
25+
const textTimeoutRef = useRef<NodeJS.Timeout | null>(null);
2226
const { t } = useTranslation();
2327
const { user } = useAuth();
28+
29+
useEffect(() => {
30+
if (authorTimeoutRef.current) clearTimeout(authorTimeoutRef.current);
31+
authorTimeoutRef.current = setTimeout(() => {
32+
setDebouncedAuthorSearch(authorSearch);
33+
}, 300);
34+
}, [authorSearch]);
35+
36+
useEffect(() => {
37+
if (textTimeoutRef.current) clearTimeout(textTimeoutRef.current);
38+
textTimeoutRef.current = setTimeout(() => {
39+
setDebouncedTextSearch(textSearch);
40+
}, 300);
41+
}, [textSearch]);
42+
2443
const loadData = async () => {
2544
if (!id) return;
2645
setIsLoading(true);
2746
setError('');
2847
try {
2948
const [topicData, postsData] = await Promise.all([
3049
getTopic(id),
31-
listPosts(id, { author: authorSearch || undefined, text: textSearch || undefined })
50+
listPosts(id, { author: debouncedAuthorSearch || undefined, text: debouncedTextSearch || undefined })
3251
]);
3352
setTopic(topicData);
3453
setPosts(postsData);
@@ -41,14 +60,19 @@ export function TopicPage() {
4160
};
4261
useEffect(() => {
4362
loadData();
44-
}, [id, authorSearch, textSearch]);
63+
}, [id, debouncedAuthorSearch, debouncedTextSearch]);
4564
const allMessages = topic ? [
4665
{ id: 'original', author: topic.author, body: topic.body, createdAt: topic.createdAt },
4766
...posts
4867
] : [];
4968
const filteredMessages = allMessages.filter(message =>
50-
(authorSearch === '' || normalizeText(message.author).includes(normalizeText(authorSearch))) &&
51-
(textSearch === '' || normalizeText(message.body).includes(normalizeText(textSearch)))
69+
(debouncedAuthorSearch === '' || normalizeText(message.author).includes(normalizeText(debouncedAuthorSearch))) &&
70+
(debouncedTextSearch === '' || (() => {
71+
const normalizedBody = normalizeText(message.body);
72+
const normalizedQuery = normalizeText(debouncedTextSearch);
73+
const words = normalizedBody.split(/\s+/);
74+
return words.some(word => word.startsWith(normalizedQuery));
75+
})())
5276
);
5377
const handleReply = async (e: React.FormEvent) => {
5478
e.preventDefault();

0 commit comments

Comments
 (0)