Обновлена логика фильтрации заметок по тегам для регистронезависимого поиска. Фильтрация по тегам теперь выполняется на клиенте, чтобы избежать проблем с кириллицей в SQLite. Также добавлено сохранение тегов в нижнем регистре для единообразия и улучшена обработка уникальных тегов в компоненте TagsFilter.
This commit is contained in:
parent
feca528d1b
commit
143338bc2b
@ -803,11 +803,13 @@ app.get("/api/notes/search", requireApiAuth, (req, res) => {
|
||||
params.push(`%${q.trim()}%`);
|
||||
}
|
||||
|
||||
// Поиск по тегу
|
||||
if (tag && tag.trim()) {
|
||||
whereClause += " AND n.content LIKE ?";
|
||||
params.push(`%#${tag.trim()}%`);
|
||||
}
|
||||
// Поиск по тегу (регистронезависимый)
|
||||
// SQLite LOWER() плохо работает с кириллицей, поэтому фильтрация по тегу выполняется на клиенте
|
||||
// Здесь не фильтруем по тегу, чтобы получить все заметки для последующей фильтрации на клиенте
|
||||
// Если есть другие фильтры (дата, поиск), они применяются здесь
|
||||
// if (tag && tag.trim()) {
|
||||
// // Фильтрация по тегу выполняется на клиенте
|
||||
// }
|
||||
|
||||
// Поиск по дате (используем created_at вместо date)
|
||||
if (date && date.trim()) {
|
||||
|
||||
@ -85,7 +85,7 @@ define(['./workbox-8cfb3eb5'], (function (workbox) { 'use strict';
|
||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "index.html",
|
||||
"revision": "0.h9luj2l3mv8"
|
||||
"revision": "0.tihabijh3s"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
|
||||
@ -978,7 +978,8 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
||||
|
||||
const handleTagClick = (e: React.MouseEvent, tag: string) => {
|
||||
e.stopPropagation();
|
||||
dispatch(setSelectedTag(tag));
|
||||
// Всегда сохраняем тег в нижнем регистре для единообразия
|
||||
dispatch(setSelectedTag(tag.toLowerCase()));
|
||||
};
|
||||
|
||||
const toggleExpand = () => {
|
||||
|
||||
@ -4,6 +4,7 @@ import { useAppSelector, useAppDispatch } from "../../store/hooks";
|
||||
import { notesApi } from "../../api/notesApi";
|
||||
import { setNotes, setAllNotes } from "../../store/slices/notesSlice";
|
||||
import { useNotification } from "../../hooks/useNotification";
|
||||
import { extractTags } from "../../utils/markdown";
|
||||
|
||||
export interface NotesListRef {
|
||||
reloadNotes: () => void;
|
||||
@ -41,6 +42,16 @@ export const NotesList = forwardRef<NotesListRef>((props, ref) => {
|
||||
if (userId) {
|
||||
notesData = notesData.filter((note) => note.user_id === userId);
|
||||
}
|
||||
|
||||
// Точная фильтрация по тегу на клиенте (регистронезависимо)
|
||||
// SQLite LOWER() плохо работает с кириллицей, поэтому фильтруем здесь
|
||||
if (selectedTag) {
|
||||
const selectedTagLower = selectedTag.toLowerCase();
|
||||
notesData = notesData.filter((note) => {
|
||||
const noteTags = extractTags(note.content);
|
||||
return noteTags.some(tag => tag.toLowerCase() === selectedTagLower);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Если нет фильтров, используем все заметки
|
||||
notesData = filteredAllData;
|
||||
|
||||
@ -14,17 +14,30 @@ export const TagsFilter: React.FC<TagsFilterProps> = ({ notes = [] }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// Получаем все уникальные теги из заметок
|
||||
// Группируем регистронезависимо, но сохраняем оригинальный регистр первого вхождения
|
||||
const getAllTags = () => {
|
||||
const tagCounts: Record<string, number> = {};
|
||||
const tagVariants: Record<string, string> = {}; // Хранит оригинальный регистр для каждого тега
|
||||
|
||||
notes.forEach((note) => {
|
||||
const tags = extractTags(note.content);
|
||||
tags.forEach((tag) => {
|
||||
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
||||
const tagKey = tag.toLowerCase();
|
||||
tagCounts[tagKey] = (tagCounts[tagKey] || 0) + 1;
|
||||
// Сохраняем первый вариант с оригинальным регистром
|
||||
if (!tagVariants[tagKey]) {
|
||||
tagVariants[tagKey] = tag;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return tagCounts;
|
||||
// Преобразуем обратно в Record с оригинальными тегами
|
||||
const result: Record<string, number> = {};
|
||||
Object.keys(tagCounts).forEach((tagKey) => {
|
||||
result[tagVariants[tagKey]] = tagCounts[tagKey];
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const tagCounts = getAllTags();
|
||||
@ -32,10 +45,12 @@ export const TagsFilter: React.FC<TagsFilterProps> = ({ notes = [] }) => {
|
||||
|
||||
const handleTagClick = (tag: string, event: React.MouseEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault();
|
||||
if (selectedTag === tag) {
|
||||
// Всегда сохраняем тег в нижнем регистре для единообразия
|
||||
const tagLower = tag.toLowerCase();
|
||||
if (selectedTag?.toLowerCase() === tagLower) {
|
||||
dispatch(setSelectedTag(null));
|
||||
} else {
|
||||
dispatch(setSelectedTag(tag));
|
||||
dispatch(setSelectedTag(tagLower));
|
||||
}
|
||||
// Сбрасываем фокус после клика для предотвращения сохранения состояния :active
|
||||
(event.currentTarget as HTMLElement).blur();
|
||||
@ -68,7 +83,7 @@ export const TagsFilter: React.FC<TagsFilterProps> = ({ notes = [] }) => {
|
||||
<div className="tags-container">
|
||||
{sortedTags.map((tag) => {
|
||||
const count = tagCounts[tag];
|
||||
const isActive = selectedTag === tag;
|
||||
const isActive = selectedTag?.toLowerCase() === tag.toLowerCase();
|
||||
|
||||
return (
|
||||
<span
|
||||
@ -90,3 +105,4 @@ export const TagsFilter: React.FC<TagsFilterProps> = ({ notes = [] }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -109,8 +109,9 @@ export const extractTags = (content: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
const tag = match[1].toLowerCase();
|
||||
if (!tags.includes(tag)) {
|
||||
const tag = match[1];
|
||||
// Проверяем, есть ли уже тег с таким же именем (регистронезависимо)
|
||||
if (!tags.some(t => t.toLowerCase() === tag.toLowerCase())) {
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user