From 05a9275253fd8e763ef4c93fb270fb6cef530d37 Mon Sep 17 00:00:00 2001 From: Fovway Date: Fri, 7 Nov 2025 22:56:07 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D1=8B=20NoteEditor,=20NoteItem=20=D0=B8=20NotesList=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B8=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BE=D0=BA=20=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=84=D0=BE=D0=BA=D1=83=D1=81=D0=B0=20=D0=BD=D0=B0=20=D1=81?= =?UTF-8?q?=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BA=D0=B8.=20=D0=98=D0=B7?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B8=D0=BF=D1=8B?= =?UTF-8?q?=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B9=20=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=B3=D0=B8=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BB?= =?UTF-8?q?=D0=B0=D0=B2=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=D1=80=D1=83=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BA?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BA=D0=B0=D0=BC.=20=D0=9E?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B8=D1=81=D0=BD=D1=8B=D0=B9=20=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BD=D0=B8=D0=BA=20=D1=81=20=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=BC=20=D1=80=D0=B5=D0=B2=D0=B8=D0=B7=D0=B8=D0=BE=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=BC=20=D0=BD=D0=BE=D0=BC=D0=B5=D1=80=D0=BE=D0=BC=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BA=D1=8D=D1=88=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev-dist/sw.js | 2 +- src/components/notes/NoteEditor.tsx | 23 +++++++++++++++++------ src/components/notes/NoteItem.tsx | 9 +++++++++ src/components/notes/NotesList.tsx | 28 ++++++++++++++++++++++++++++ src/pages/NotesPage.tsx | 24 +++++++++++++++++++++++- 5 files changed, 78 insertions(+), 8 deletions(-) diff --git a/dev-dist/sw.js b/dev-dist/sw.js index 6cdc433..49ebada 100644 --- a/dev-dist/sw.js +++ b/dev-dist/sw.js @@ -82,7 +82,7 @@ define(['./workbox-47da91e0'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "/index.html", - "revision": "0.9eood2uf828" + "revision": "0.9t777mpmecg" }], { "ignoreURLParametersMatching": [/^utm_/, /^fbclid$/] }); diff --git a/src/components/notes/NoteEditor.tsx b/src/components/notes/NoteEditor.tsx index 784c33c..630829a 100644 --- a/src/components/notes/NoteEditor.tsx +++ b/src/components/notes/NoteEditor.tsx @@ -14,7 +14,7 @@ import { ImproveTextModal } from "./ImproveTextModal"; import { extractTags } from "../../utils/markdown"; interface NoteEditorProps { - onSave: () => void; + onSave: (noteId?: number | string) => void; } export const NoteEditor: React.FC = ({ onSave }) => { @@ -80,7 +80,7 @@ export const NoteEditor: React.FC = ({ onSave }) => { setContent(""); setImages([]); setFiles([]); - onSave(); + onSave(note.id); } catch (error) { console.error("Ошибка сохранения заметки:", error); showNotification("Ошибка сохранения заметки", "error"); @@ -106,7 +106,9 @@ export const NoteEditor: React.FC = ({ onSave }) => { console.error("Ошибка улучшения текста:", error); setImproveError(true); setImproveErrorMessage( - error.response?.data?.error || error.message || "Ошибка улучшения текста" + error.response?.data?.error || + error.message || + "Ошибка улучшения текста" ); } finally { setIsAiLoading(false); @@ -147,7 +149,10 @@ export const NoteEditor: React.FC = ({ onSave }) => { console.error("Детали ошибки:", error.response?.data); setTagsGenerationError(true); setShowTagsModal(false); - const errorMessage = error.response?.data?.error || error.message || "Ошибка генерации тегов"; + const errorMessage = + error.response?.data?.error || + error.message || + "Ошибка генерации тегов"; showNotification(errorMessage, "error"); } finally { setIsGeneratingTags(false); @@ -159,13 +164,19 @@ export const NoteEditor: React.FC = ({ onSave }) => { const existingTags = extractTags(content); const tagsToAdd = tags - .filter((tag) => !existingTags.some((existing) => existing.toLowerCase() === tag.toLowerCase())) + .filter( + (tag) => + !existingTags.some( + (existing) => existing.toLowerCase() === tag.toLowerCase() + ) + ) .map((tag) => `#${tag}`) .join(" "); if (tagsToAdd) { // Добавляем теги в конец заметки - const newContent = content.trim() + (content.trim() ? "\n\n" : "") + tagsToAdd; + const newContent = + content.trim() + (content.trim() ? "\n\n" : "") + tagsToAdd; setContent(newContent); showNotification(`Добавлено тегов: ${tags.length}`, "success"); } else { diff --git a/src/components/notes/NoteItem.tsx b/src/components/notes/NoteItem.tsx index 0733793..14f6aea 100644 --- a/src/components/notes/NoteItem.tsx +++ b/src/components/notes/NoteItem.tsx @@ -31,6 +31,7 @@ interface NoteItemProps { onReload: () => void; isSelected?: boolean; onSelect?: (id: number | string) => void; + onFocusNote?: (id: number | string) => void; } export const NoteItem: React.FC = ({ @@ -41,6 +42,7 @@ export const NoteItem: React.FC = ({ onReload, isSelected = false, onSelect, + onFocusNote, }) => { const [isEditing, setIsEditing] = useState(false); const [editContent, setEditContent] = useState(note.content); @@ -138,6 +140,13 @@ export const NoteItem: React.FC = ({ setDeletedImageIds([]); setDeletedFileIds([]); onReload(); + // Устанавливаем фокус на заметку после сохранения + if (onFocusNote) { + // Небольшая задержка для завершения обновления DOM + setTimeout(() => { + onFocusNote(note.id); + }, 100); + } } catch (error) { console.error("Ошибка обновления заметки:", error); showNotification("Ошибка обновления заметки", "error"); diff --git a/src/components/notes/NotesList.tsx b/src/components/notes/NotesList.tsx index 2841251..1cdf225 100644 --- a/src/components/notes/NotesList.tsx +++ b/src/components/notes/NotesList.tsx @@ -8,6 +8,7 @@ import { extractTags } from "../../utils/markdown"; export interface NotesListRef { reloadNotes: () => void; + focusNote: (noteId: number | string) => void; } interface NotesListProps { @@ -81,8 +82,34 @@ export const NotesList = forwardRef( // eslint-disable-next-line react-hooks/exhaustive-deps }, [userId, searchQuery, selectedDate, selectedTag]); + const focusNote = (noteId: number | string) => { + // Используем data-атрибут для поиска элемента заметки + const noteElement = document.querySelector( + `[data-note-id="${noteId}"]` + ) as HTMLDivElement | null; + + if (noteElement) { + // Прокручиваем к верхней части заметки + noteElement.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" }); + // Добавляем визуальное выделение на короткое время + // Очищаем предыдущий таймер, если он был установлен + const existingTimeout = (noteElement as any).__focusTimeout; + if (existingTimeout) { + clearTimeout(existingTimeout); + } + noteElement.style.transition = "box-shadow 0.3s ease"; + noteElement.style.boxShadow = "0 0 0 3px rgba(33, 150, 243, 0.5)"; + const timeout = setTimeout(() => { + noteElement.style.boxShadow = ""; + (noteElement as any).__focusTimeout = null; + }, 2000); + (noteElement as any).__focusTimeout = timeout; + } + }; + useImperativeHandle(ref, () => ({ reloadNotes: loadNotes, + focusNote: focusNote, })); const handleDelete = async (id: number | string) => { @@ -167,6 +194,7 @@ export const NotesList = forwardRef( onReload={loadNotes} isSelected={selectedNoteIds.includes(note.id)} onSelect={onNoteSelect} + onFocusNote={focusNote} /> ))} diff --git a/src/pages/NotesPage.tsx b/src/pages/NotesPage.tsx index 6a1dba8..6018722 100644 --- a/src/pages/NotesPage.tsx +++ b/src/pages/NotesPage.tsx @@ -63,10 +63,32 @@ const NotesPage: React.FC = () => { const activeFilters = getActiveFilters(); - const handleNoteSave = () => { + const handleNoteSave = (noteId?: number | string) => { // Вызываем перезагрузку заметок после создания новой заметки if (notesListRef.current) { notesListRef.current.reloadNotes(); + // Устанавливаем фокус на сохраненную заметку после небольшой задержки, + // чтобы дать время для перезагрузки и рендеринга заметок + if (noteId) { + let attempts = 0; + const maxAttempts = 15; + const attemptFocus = () => { + attempts++; + if (notesListRef.current) { + // Проверяем, есть ли элемент заметки в DOM + const noteElement = document.querySelector( + `[data-note-id="${noteId}"]` + ); + if (noteElement || attempts >= maxAttempts) { + notesListRef.current.focusNote(noteId); + } else if (attempts < maxAttempts) { + // Пытаемся еще раз, если заметка еще не появилась в DOM + setTimeout(attemptFocus, 100); + } + } + }; + setTimeout(attemptFocus, 200); + } } };