Добавлена поддержка колонки floating_toolbar_enabled в таблицу пользователей и обновлены соответствующие компоненты для управления отображением плавающей панели инструментов. Реализована логика настройки и сохранения этого параметра в профиле пользователя. Обновлены API и интерфейсы для поддержки новых функций.

This commit is contained in:
Fovway 2025-11-05 05:47:12 +07:00
parent e49b9dc865
commit 80c42f8df0
7 changed files with 112 additions and 28 deletions

View File

@ -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 // Проверяем существование колонки ai_enabled
const hasAiEnabled = columns.some((col) => col.name === "ai_enabled"); const hasAiEnabled = columns.some((col) => col.name === "ai_enabled");
if (!hasAiEnabled) { if (!hasAiEnabled) {
@ -771,7 +793,7 @@ app.get("/api/user", requireApiAuth, (req, res) => {
} }
const sql = 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) => { db.get(sql, [req.session.userId], (err, user) => {
if (err) { if (err) {
console.error("Ошибка получения данных пользователя:", err.message); console.error("Ошибка получения данных пользователя:", err.message);
@ -1600,6 +1622,7 @@ app.put("/api/user/profile", requireApiAuth, async (req, res) => {
accent_color, accent_color,
show_edit_date, show_edit_date,
colored_icons, colored_icons,
floating_toolbar_enabled,
} = req.body; } = req.body;
const userId = req.session.userId; const userId = req.session.userId;
@ -1675,6 +1698,11 @@ app.put("/api/user/profile", requireApiAuth, async (req, res) => {
params.push(colored_icons ? 1 : 0); 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) { if (newPassword) {
const hashedPassword = await bcrypt.hash(newPassword, 10); const hashedPassword = await bcrypt.hash(newPassword, 10);
updateFields.push("password = ?"); updateFields.push("password = ?");

View File

@ -82,7 +82,7 @@ define(['./workbox-9dc17825'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812" "revision": "3ca0b8505b4bec776b69afdba2768812"
}, { }, {
"url": "index.html", "url": "index.html",
"revision": "0.b944c6vblpo" "revision": "0.2vg2p27g3bg"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

View File

@ -14,6 +14,7 @@ export const userApi = {
accent_color?: string; accent_color?: string;
show_edit_date?: boolean; show_edit_date?: boolean;
colored_icons?: boolean; colored_icons?: boolean;
floating_toolbar_enabled?: boolean;
} }
) => { ) => {
const { data } = await axiosClient.put("/user/profile", profile); const { data } = await axiosClient.put("/user/profile", profile);

View File

@ -4,7 +4,7 @@ import { FloatingToolbar } from "./FloatingToolbar";
import { NotePreview } from "./NotePreview"; import { NotePreview } from "./NotePreview";
import { ImageUpload } from "./ImageUpload"; import { ImageUpload } from "./ImageUpload";
import { FileUpload } from "./FileUpload"; import { FileUpload } from "./FileUpload";
import { useAppSelector, useAppDispatch } from "../../store/hooks"; import { useAppSelector } from "../../store/hooks";
import { useNotification } from "../../hooks/useNotification"; import { useNotification } from "../../hooks/useNotification";
import { offlineNotesApi } from "../../api/offlineNotesApi"; import { offlineNotesApi } from "../../api/offlineNotesApi";
import { aiApi } from "../../api/aiApi"; import { aiApi } from "../../api/aiApi";
@ -31,7 +31,13 @@ export const NoteEditor: React.FC<NoteEditorProps> = ({ onSave }) => {
const isPreviewMode = useAppSelector((state) => state.ui.isPreviewMode); const isPreviewMode = useAppSelector((state) => state.ui.isPreviewMode);
const { showNotification } = useNotification(); const { showNotification } = useNotification();
const aiEnabled = useAppSelector((state) => state.profile.aiEnabled); 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 () => { const handleSave = async () => {
if (!content.trim()) { if (!content.trim()) {
@ -684,7 +690,7 @@ export const NoteEditor: React.FC<NoteEditorProps> = ({ onSave }) => {
// Обработчик выделения текста // Обработчик выделения текста
const handleSelection = useCallback(() => { const handleSelection = useCallback(() => {
if (isPreviewMode) { if (isPreviewMode || !floatingToolbarEnabled) {
setShowFloatingToolbar(false); setShowFloatingToolbar(false);
return; return;
} }
@ -709,7 +715,13 @@ export const NoteEditor: React.FC<NoteEditorProps> = ({ onSave }) => {
setHasSelection(false); setHasSelection(false);
setActiveFormats({ bold: false, italic: false, strikethrough: false }); setActiveFormats({ bold: false, italic: false, strikethrough: false });
} }
}, [isPreviewMode, content, getCursorPosition, getActiveFormats]); }, [
isPreviewMode,
content,
getCursorPosition,
getActiveFormats,
floatingToolbarEnabled,
]);
// Отслеживание выделения текста // Отслеживание выделения текста
useEffect(() => { useEffect(() => {
@ -955,16 +967,18 @@ export const NoteEditor: React.FC<NoteEditorProps> = ({ onSave }) => {
} }
}} }}
/> />
<FloatingToolbar {floatingToolbarEnabled && (
textareaRef={textareaRef} <FloatingToolbar
onFormat={insertMarkdown} textareaRef={textareaRef}
visible={showFloatingToolbar} onFormat={insertMarkdown}
position={toolbarPosition} visible={showFloatingToolbar}
onHide={() => setShowFloatingToolbar(false)} position={toolbarPosition}
onInsertColor={insertColorMarkdown} onHide={() => setShowFloatingToolbar(false)}
activeFormats={activeFormats} onInsertColor={insertColorMarkdown}
hasSelection={hasSelection} activeFormats={activeFormats}
/> hasSelection={hasSelection}
/>
)}
</> </>
)} )}

View File

@ -67,6 +67,12 @@ export const NoteItem: React.FC<NoteItemProps> = ({
const { showNotification } = useNotification(); const { showNotification } = useNotification();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
useMarkdown({ onNoteUpdate: onReload }); // Инициализируем обработчики спойлеров, внешних ссылок и чекбоксов useMarkdown({ onNoteUpdate: onReload }); // Инициализируем обработчики спойлеров, внешних ссылок и чекбоксов
// Проверяем, включена ли плавающая панель
const floatingToolbarEnabled =
user?.floating_toolbar_enabled !== undefined
? user.floating_toolbar_enabled === 1
: true;
const handleEdit = () => { const handleEdit = () => {
setIsEditing(true); setIsEditing(true);
@ -594,7 +600,7 @@ export const NoteItem: React.FC<NoteItemProps> = ({
// Обработчик выделения текста // Обработчик выделения текста
const handleSelection = useCallback(() => { const handleSelection = useCallback(() => {
if (localPreviewMode) { if (localPreviewMode || !floatingToolbarEnabled) {
setShowFloatingToolbar(false); setShowFloatingToolbar(false);
return; return;
} }
@ -619,7 +625,7 @@ export const NoteItem: React.FC<NoteItemProps> = ({
setHasSelection(false); setHasSelection(false);
setActiveFormats({ bold: false, italic: false, strikethrough: false }); setActiveFormats({ bold: false, italic: false, strikethrough: false });
} }
}, [localPreviewMode, editContent, getCursorPosition, getActiveFormats]); }, [localPreviewMode, editContent, getCursorPosition, getActiveFormats, floatingToolbarEnabled]);
const handleImageButtonClick = () => { const handleImageButtonClick = () => {
imageInputRef.current?.click(); imageInputRef.current?.click();
@ -1221,16 +1227,18 @@ export const NoteItem: React.FC<NoteItemProps> = ({
} }
}} }}
/> />
<FloatingToolbar {floatingToolbarEnabled && (
textareaRef={editTextareaRef} <FloatingToolbar
onFormat={insertMarkdown} textareaRef={editTextareaRef}
visible={showFloatingToolbar} onFormat={insertMarkdown}
position={toolbarPosition} visible={showFloatingToolbar}
onHide={() => setShowFloatingToolbar(false)} position={toolbarPosition}
onInsertColor={insertColorMarkdown} onHide={() => setShowFloatingToolbar(false)}
activeFormats={activeFormats} onInsertColor={insertColorMarkdown}
hasSelection={hasSelection} activeFormats={activeFormats}
/> hasSelection={hasSelection}
/>
)}
</> </>
)} )}

View File

@ -46,6 +46,7 @@ const SettingsPage: React.FC = () => {
const [selectedAccentColor, setSelectedAccentColor] = useState("#007bff"); const [selectedAccentColor, setSelectedAccentColor] = useState("#007bff");
const [showEditDate, setShowEditDate] = useState(true); const [showEditDate, setShowEditDate] = useState(true);
const [coloredIcons, setColoredIcons] = useState(true); const [coloredIcons, setColoredIcons] = useState(true);
const [floatingToolbarEnabled, setFloatingToolbarEnabled] = useState(true);
// AI settings // AI settings
const [apiKey, setApiKey] = useState(""); const [apiKey, setApiKey] = useState("");
@ -134,6 +135,11 @@ const SettingsPage: React.FC = () => {
: true; : true;
setColoredIcons(coloredIconsValue); setColoredIcons(coloredIconsValue);
updateColoredIconsClass(coloredIconsValue); updateColoredIconsClass(coloredIconsValue);
const floatingToolbarValue =
userData.floating_toolbar_enabled !== undefined
? userData.floating_toolbar_enabled === 1
: true;
setFloatingToolbarEnabled(floatingToolbarValue);
// Загружаем AI настройки // Загружаем AI настройки
try { try {
@ -166,6 +172,7 @@ const SettingsPage: React.FC = () => {
accent_color: selectedAccentColor, accent_color: selectedAccentColor,
show_edit_date: showEditDate, show_edit_date: showEditDate,
colored_icons: coloredIcons, colored_icons: coloredIcons,
floating_toolbar_enabled: floatingToolbarEnabled,
}); });
dispatch(setAccentColorAction(selectedAccentColor)); dispatch(setAccentColorAction(selectedAccentColor));
setAccentColor(selectedAccentColor); setAccentColor(selectedAccentColor);
@ -672,6 +679,31 @@ const SettingsPage: React.FC = () => {
</label> </label>
</div> </div>
<div className="form-group ai-toggle-group">
<label className="ai-toggle-label">
<div className="toggle-label-content">
<span className="toggle-text-main">
Плавающая панель редактирования
</span>
<span className="toggle-text-desc">
{floatingToolbarEnabled
? "Показывать плавающую панель инструментов при выделении текста в редакторе"
: "Скрывать плавающую панель инструментов при выделении текста"}
</span>
</div>
<div className="toggle-switch-wrapper">
<input
type="checkbox"
id="floating-toolbar-toggle"
className="toggle-checkbox"
checked={floatingToolbarEnabled}
onChange={(e) => setFloatingToolbarEnabled(e.target.checked)}
/>
<span className="toggle-slider"></span>
</div>
</label>
</div>
<button className="btnSave" onClick={handleUpdateAppearance}> <button className="btnSave" onClick={handleUpdateAppearance}>
Сохранить изменения Сохранить изменения
</button> </button>

View File

@ -5,6 +5,7 @@ export interface User {
accent_color: string; accent_color: string;
show_edit_date?: number; show_edit_date?: number;
colored_icons?: number; colored_icons?: number;
floating_toolbar_enabled?: number;
} }
export interface AuthResponse { export interface AuthResponse {