From 80c42f8df01a1d4f5512d9109796ed391d528cbb Mon Sep 17 00:00:00 2001 From: Fovway Date: Wed, 5 Nov 2025 05:47:12 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD=D0=BA=D0=B8=20floating?= =?UTF-8?q?=5Ftoolbar=5Fenabled=20=D0=B2=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D1=86=D1=83=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=B8=20=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=81=D0=BE=D0=BE=D1=82?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D1=83=D1=8E=D1=89=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=BF=D0=BB=D0=B0?= =?UTF-8?q?=D0=B2=D0=B0=D1=8E=D1=89=D0=B5=D0=B9=20=D0=BF=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=BE=D0=B2.=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=8D=D1=82=D0=BE=D0=B3=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=B0=20=D0=B2=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=84=D0=B8=D0=BB=D0=B5=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F.=20?= =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20API=20?= =?UTF-8?q?=D0=B8=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=BA=D0=B8=20=D0=BD=D0=BE=D0=B2=D1=8B=D1=85=20?= =?UTF-8?q?=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.js | 30 ++++++++++++++++++++- dev-dist/sw.js | 2 +- src/api/userApi.ts | 1 + src/components/notes/NoteEditor.tsx | 42 +++++++++++++++++++---------- src/components/notes/NoteItem.tsx | 32 +++++++++++++--------- src/pages/SettingsPage.tsx | 32 ++++++++++++++++++++++ src/types/user.ts | 1 + 7 files changed, 112 insertions(+), 28 deletions(-) diff --git a/backend/server.js b/backend/server.js index b93897a..e174b1e 100644 --- a/backend/server.js +++ b/backend/server.js @@ -502,6 +502,28 @@ function runMigrations() { ); } + // Проверяем существование колонки floating_toolbar_enabled + const hasFloatingToolbarEnabled = columns.some( + (col) => col.name === "floating_toolbar_enabled" + ); + if (!hasFloatingToolbarEnabled) { + db.run( + "ALTER TABLE users ADD COLUMN floating_toolbar_enabled INTEGER DEFAULT 1", + (err) => { + if (err) { + console.error( + "Ошибка добавления колонки floating_toolbar_enabled:", + err.message + ); + } else { + console.log( + "Колонка floating_toolbar_enabled добавлена в таблицу users" + ); + } + } + ); + } + // Проверяем существование колонки ai_enabled const hasAiEnabled = columns.some((col) => col.name === "ai_enabled"); if (!hasAiEnabled) { @@ -771,7 +793,7 @@ app.get("/api/user", requireApiAuth, (req, res) => { } const sql = - "SELECT username, email, avatar, accent_color, show_edit_date, colored_icons FROM users WHERE id = ?"; + "SELECT username, email, avatar, accent_color, show_edit_date, colored_icons, floating_toolbar_enabled FROM users WHERE id = ?"; db.get(sql, [req.session.userId], (err, user) => { if (err) { console.error("Ошибка получения данных пользователя:", err.message); @@ -1600,6 +1622,7 @@ app.put("/api/user/profile", requireApiAuth, async (req, res) => { accent_color, show_edit_date, colored_icons, + floating_toolbar_enabled, } = req.body; const userId = req.session.userId; @@ -1675,6 +1698,11 @@ app.put("/api/user/profile", requireApiAuth, async (req, res) => { params.push(colored_icons ? 1 : 0); } + if (floating_toolbar_enabled !== undefined) { + updateFields.push("floating_toolbar_enabled = ?"); + params.push(floating_toolbar_enabled ? 1 : 0); + } + if (newPassword) { const hashedPassword = await bcrypt.hash(newPassword, 10); updateFields.push("password = ?"); diff --git a/dev-dist/sw.js b/dev-dist/sw.js index 1a0abe6..79f7624 100644 --- a/dev-dist/sw.js +++ b/dev-dist/sw.js @@ -82,7 +82,7 @@ define(['./workbox-9dc17825'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.b944c6vblpo" + "revision": "0.2vg2p27g3bg" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/src/api/userApi.ts b/src/api/userApi.ts index 3655742..d940cc1 100644 --- a/src/api/userApi.ts +++ b/src/api/userApi.ts @@ -14,6 +14,7 @@ export const userApi = { accent_color?: string; show_edit_date?: boolean; colored_icons?: boolean; + floating_toolbar_enabled?: boolean; } ) => { const { data } = await axiosClient.put("/user/profile", profile); diff --git a/src/components/notes/NoteEditor.tsx b/src/components/notes/NoteEditor.tsx index bd8b398..a397e77 100644 --- a/src/components/notes/NoteEditor.tsx +++ b/src/components/notes/NoteEditor.tsx @@ -4,7 +4,7 @@ import { FloatingToolbar } from "./FloatingToolbar"; import { NotePreview } from "./NotePreview"; import { ImageUpload } from "./ImageUpload"; import { FileUpload } from "./FileUpload"; -import { useAppSelector, useAppDispatch } from "../../store/hooks"; +import { useAppSelector } from "../../store/hooks"; import { useNotification } from "../../hooks/useNotification"; import { offlineNotesApi } from "../../api/offlineNotesApi"; import { aiApi } from "../../api/aiApi"; @@ -31,7 +31,13 @@ export const NoteEditor: React.FC = ({ onSave }) => { const isPreviewMode = useAppSelector((state) => state.ui.isPreviewMode); const { showNotification } = useNotification(); const aiEnabled = useAppSelector((state) => state.profile.aiEnabled); - const dispatch = useAppDispatch(); + const user = useAppSelector((state) => state.profile.user); + + // Проверяем, включена ли плавающая панель + const floatingToolbarEnabled = + user?.floating_toolbar_enabled !== undefined + ? user.floating_toolbar_enabled === 1 + : true; const handleSave = async () => { if (!content.trim()) { @@ -684,7 +690,7 @@ export const NoteEditor: React.FC = ({ onSave }) => { // Обработчик выделения текста const handleSelection = useCallback(() => { - if (isPreviewMode) { + if (isPreviewMode || !floatingToolbarEnabled) { setShowFloatingToolbar(false); return; } @@ -709,7 +715,13 @@ export const NoteEditor: React.FC = ({ onSave }) => { setHasSelection(false); setActiveFormats({ bold: false, italic: false, strikethrough: false }); } - }, [isPreviewMode, content, getCursorPosition, getActiveFormats]); + }, [ + isPreviewMode, + content, + getCursorPosition, + getActiveFormats, + floatingToolbarEnabled, + ]); // Отслеживание выделения текста useEffect(() => { @@ -955,16 +967,18 @@ export const NoteEditor: React.FC = ({ onSave }) => { } }} /> - setShowFloatingToolbar(false)} - onInsertColor={insertColorMarkdown} - activeFormats={activeFormats} - hasSelection={hasSelection} - /> + {floatingToolbarEnabled && ( + setShowFloatingToolbar(false)} + onInsertColor={insertColorMarkdown} + activeFormats={activeFormats} + hasSelection={hasSelection} + /> + )} )} diff --git a/src/components/notes/NoteItem.tsx b/src/components/notes/NoteItem.tsx index 4311ae7..bae83ff 100644 --- a/src/components/notes/NoteItem.tsx +++ b/src/components/notes/NoteItem.tsx @@ -67,6 +67,12 @@ export const NoteItem: React.FC = ({ const { showNotification } = useNotification(); const dispatch = useAppDispatch(); useMarkdown({ onNoteUpdate: onReload }); // Инициализируем обработчики спойлеров, внешних ссылок и чекбоксов + + // Проверяем, включена ли плавающая панель + const floatingToolbarEnabled = + user?.floating_toolbar_enabled !== undefined + ? user.floating_toolbar_enabled === 1 + : true; const handleEdit = () => { setIsEditing(true); @@ -594,7 +600,7 @@ export const NoteItem: React.FC = ({ // Обработчик выделения текста const handleSelection = useCallback(() => { - if (localPreviewMode) { + if (localPreviewMode || !floatingToolbarEnabled) { setShowFloatingToolbar(false); return; } @@ -619,7 +625,7 @@ export const NoteItem: React.FC = ({ setHasSelection(false); setActiveFormats({ bold: false, italic: false, strikethrough: false }); } - }, [localPreviewMode, editContent, getCursorPosition, getActiveFormats]); + }, [localPreviewMode, editContent, getCursorPosition, getActiveFormats, floatingToolbarEnabled]); const handleImageButtonClick = () => { imageInputRef.current?.click(); @@ -1221,16 +1227,18 @@ export const NoteItem: React.FC = ({ } }} /> - setShowFloatingToolbar(false)} - onInsertColor={insertColorMarkdown} - activeFormats={activeFormats} - hasSelection={hasSelection} - /> + {floatingToolbarEnabled && ( + setShowFloatingToolbar(false)} + onInsertColor={insertColorMarkdown} + activeFormats={activeFormats} + hasSelection={hasSelection} + /> + )} )} diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 157b8f2..48a0cda 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -46,6 +46,7 @@ const SettingsPage: React.FC = () => { const [selectedAccentColor, setSelectedAccentColor] = useState("#007bff"); const [showEditDate, setShowEditDate] = useState(true); const [coloredIcons, setColoredIcons] = useState(true); + const [floatingToolbarEnabled, setFloatingToolbarEnabled] = useState(true); // AI settings const [apiKey, setApiKey] = useState(""); @@ -134,6 +135,11 @@ const SettingsPage: React.FC = () => { : true; setColoredIcons(coloredIconsValue); updateColoredIconsClass(coloredIconsValue); + const floatingToolbarValue = + userData.floating_toolbar_enabled !== undefined + ? userData.floating_toolbar_enabled === 1 + : true; + setFloatingToolbarEnabled(floatingToolbarValue); // Загружаем AI настройки try { @@ -166,6 +172,7 @@ const SettingsPage: React.FC = () => { accent_color: selectedAccentColor, show_edit_date: showEditDate, colored_icons: coloredIcons, + floating_toolbar_enabled: floatingToolbarEnabled, }); dispatch(setAccentColorAction(selectedAccentColor)); setAccentColor(selectedAccentColor); @@ -672,6 +679,31 @@ const SettingsPage: React.FC = () => { +
+ +
+ diff --git a/src/types/user.ts b/src/types/user.ts index 8033656..bf9ee53 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -5,6 +5,7 @@ export interface User { accent_color: string; show_edit_date?: number; colored_icons?: number; + floating_toolbar_enabled?: number; } export interface AuthResponse {