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