diff --git a/package.json b/package.json index fb971cc..8f358a1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vite-react-typescript-starter", "private": true, - "version": "1.2.10", + "version": "1.2.11", "type": "module", "scripts": { "dev": "vite", diff --git a/public/robots.txt b/public/robots.txt index 03a2b64..8c05237 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -6,6 +6,8 @@ Disallow: /admin/ Disallow: /admin/* Disallow: /search Disallow: /search* +Disallow: /bookmarks +Disallow: /bookmarks* Disallow: /*?page= Disallow: /*?category= diff --git a/src/components/AuthorSelectionModal.tsx b/src/components/AuthorSelectionModal.tsx index 56ea906..35a7c1a 100644 --- a/src/components/AuthorSelectionModal.tsx +++ b/src/components/AuthorSelectionModal.tsx @@ -26,10 +26,26 @@ const AuthorSelectionModal: React.FC = ({ existingAuthors, }) => { const [activeTab, setActiveTab] = useState(AuthorRole.WRITER); - const [selectedAuthorId, setSelectedAuthorId] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [searchTerm, setSearchTerm] = useState(''); - const itemsPerPage = 10; + const itemsPerPage = 6; + + // Обработчик нажатия клавиши Esc + useEffect(() => { + const handleEscKey = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onClose(); + } + }; + + if (isOpen) { + document.addEventListener('keydown', handleEscKey); + } + + return () => { + document.removeEventListener('keydown', handleEscKey); + }; + }, [isOpen, onClose]); // Используем useMemo для оптимальной фильтрации и сортировки авторов const { popularAuthors, otherAuthors } = useMemo(() => { @@ -39,7 +55,7 @@ const AuthorSelectionModal: React.FC = ({ !existingAuthors.some(existing => existing.authorId === author.id && existing.role === activeTab) ); - // Сортируем по полю order (по возрастанию) и берем первые 6 + // Сортируем по полю order (по возрастанию) и берем первые 5 const sortedByOrder = [...filteredAuthors].sort((a, b) => a.order - b.order); const popularAuthors = sortedByOrder.slice(0, 6); @@ -51,6 +67,20 @@ const AuthorSelectionModal: React.FC = ({ return { popularAuthors, otherAuthors }; }, [authors, existingAuthors, activeTab]); + // Получаем уникальные первые буквы имен остальных авторов + const firstLetters = useMemo(() => { + const letters = new Set(); + + otherAuthors.forEach(author => { + // Берем первую букву имени, приводим к верхнему регистру + const firstLetter = author.displayName.charAt(0).toUpperCase(); + letters.add(firstLetter); + }); + + // Преобразуем Set в массив и сортируем + return Array.from(letters).sort(); + }, [otherAuthors]); + // Фильтруем остальных авторов по поисковому запросу const filteredOtherAuthors = useMemo(() => { if (!searchTerm.trim()) { @@ -65,27 +95,17 @@ const AuthorSelectionModal: React.FC = ({ // Вычисляем данные для пагинации только для отфильтрованных остальных авторов const paginationData = useMemo(() => { - const showPagination = filteredOtherAuthors.length > itemsPerPage; - - // Если пагинация не нужна, показываем всех отфильтрованных остальных авторов - if (!showPagination) { - return { - showPagination: false, - displayedOtherAuthors: filteredOtherAuthors, - totalPages: 1 - }; - } - - // Если пагинация нужна - const totalPages = Math.ceil(filteredOtherAuthors.length / itemsPerPage); + const totalAuthors = filteredOtherAuthors.length; + const totalPages = Math.ceil(totalAuthors / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const displayedOtherAuthors = filteredOtherAuthors.slice(startIndex, endIndex); return { - showPagination: true, + totalAuthors, + totalPages, displayedOtherAuthors, - totalPages + showPagination: totalPages > 1 }; }, [filteredOtherAuthors, currentPage, itemsPerPage]); @@ -94,19 +114,16 @@ const AuthorSelectionModal: React.FC = ({ setCurrentPage(1); }, [searchTerm]); - const handleAddAuthor = () => { - if (selectedAuthorId) { - onAddAuthor(selectedAuthorId, activeTab); - setSelectedAuthorId(null); - onClose(); - } + // Обработчик выбора автора + const handleAuthorSelect = (authorId: string) => { + onAddAuthor(authorId, activeTab); + onClose(); }; const handleTabChange = (role: AuthorRole) => { setActiveTab(role); setCurrentPage(1); setSearchTerm(''); - setSelectedAuthorId(null); }; const handlePageChange = (page: number) => { @@ -123,9 +140,13 @@ const AuthorSelectionModal: React.FC = ({ setSearchTerm(''); }; + const handleLetterClick = (letter: string) => { + setSearchTerm(letter.toLowerCase()); + }; + if (!isOpen) return null; - const { showPagination, displayedOtherAuthors, totalPages } = paginationData; + const { totalAuthors, totalPages, displayedOtherAuthors, showPagination } = paginationData; return (
@@ -174,7 +195,7 @@ const AuthorSelectionModal: React.FC = ({
{/* Популярные авторы (всегда отображаются, если есть) */} {popularAuthors.length > 0 && ( -
+

Популярные

@@ -191,8 +212,7 @@ const AuthorSelectionModal: React.FC = ({ setSelectedAuthorId(author.id)} + onSelect={() => handleAuthorSelect(author.id)} showOrder={true} /> @@ -202,10 +222,10 @@ const AuthorSelectionModal: React.FC = ({ )} {/* Все остальные авторы с поиском и пагинацией */} -
+

- Остальные {roleLabels[activeTab].toLowerCase()} + Все {roleLabels[activeTab].toLowerCase()}

{showPagination && ( @@ -214,6 +234,37 @@ const AuthorSelectionModal: React.FC = ({ )}
+ {/* Кнопки с первыми буквами */} + {firstLetters.length > 0 && ( +
+
+ + {firstLetters.map(letter => ( + + ))} +
+
+ )} + {/* Поле поиска */}
@@ -238,7 +289,7 @@ const AuthorSelectionModal: React.FC = ({
{searchTerm && (

- Найдено: {filteredOtherAuthors.length} {filteredOtherAuthors.length === 1 ? 'автор' : 'авторов'} + Найдено: {totalAuthors} {totalAuthors === 1 ? 'автор' : 'авторов'}

)}
@@ -250,15 +301,14 @@ const AuthorSelectionModal: React.FC = ({ setSelectedAuthorId(author.id)} + onSelect={() => handleAuthorSelect(author.id)} showOrder={false} /> ))}
{/* Пагинация */} - {showPagination && totalPages > 1 && ( + {showPagination && (
- - {/* Кнопки действий */} -
- - -
); }; -// Компонент карточки автора (без изменений) +// Обновленный компонент карточки автора interface AuthorCardProps { author: Author; - isSelected: boolean; onSelect: () => void; showOrder: boolean; } -const AuthorCard: React.FC = ({ author, isSelected, onSelect, showOrder }) => { +const AuthorCard: React.FC = ({ author, onSelect, showOrder }) => { return (