Упрощены импорты и улучшена типизация в компонентах. Удалены неиспользуемые зависимости и оптимизирована логика обработки идентификаторов в компонентах заметок. Обновлены стили для повышения производительности и улучшения пользовательского опыта.
This commit is contained in:
parent
87a01629ae
commit
4d91b2227d
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
format,
|
format,
|
||||||
startOfMonth,
|
startOfMonth,
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import React from "react";
|
|||||||
import { MiniCalendar } from "../calendar/MiniCalendar";
|
import { MiniCalendar } from "../calendar/MiniCalendar";
|
||||||
import { SearchBar } from "../search/SearchBar";
|
import { SearchBar } from "../search/SearchBar";
|
||||||
import { TagsFilter } from "../search/TagsFilter";
|
import { TagsFilter } from "../search/TagsFilter";
|
||||||
import { useAppSelector } from "../../store/hooks";
|
|
||||||
import { Note } from "../../types/note";
|
import { Note } from "../../types/note";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
|
|||||||
@ -113,7 +113,14 @@ export const MarkdownToolbar: React.FC<MarkdownToolbarProps> = ({
|
|||||||
};
|
};
|
||||||
}, [isDragging]);
|
}, [isDragging]);
|
||||||
|
|
||||||
const buttons = [];
|
const buttons: Array<{
|
||||||
|
id: string;
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
before?: string;
|
||||||
|
after?: string;
|
||||||
|
action?: () => void;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -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,6 @@ 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 handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!content.trim()) {
|
if (!content.trim()) {
|
||||||
@ -251,36 +250,27 @@ export const NoteEditor: React.FC<NoteEditorProps> = ({ onSave }) => {
|
|||||||
|
|
||||||
// Определяем, есть ли уже такие маркеры на всех строках
|
// Определяем, есть ли уже такие маркеры на всех строках
|
||||||
let allLinesHaveMarker = true;
|
let allLinesHaveMarker = true;
|
||||||
let hasAnyMarker = false;
|
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmedLine = line.trimStart();
|
const trimmedLine = line.trimStart();
|
||||||
if (before === "- ") {
|
if (before === "- ") {
|
||||||
// Для маркированного списка проверяем различные варианты
|
// Для маркированного списка проверяем различные варианты
|
||||||
if (trimmedLine.match(/^[-*+]\s/)) {
|
if (!trimmedLine.match(/^[-*+]\s/)) {
|
||||||
hasAnyMarker = true;
|
|
||||||
} else {
|
|
||||||
allLinesHaveMarker = false;
|
allLinesHaveMarker = false;
|
||||||
}
|
}
|
||||||
} else if (before === "1. ") {
|
} else if (before === "1. ") {
|
||||||
// Для нумерованного списка
|
// Для нумерованного списка
|
||||||
if (trimmedLine.match(/^\d+\.\s/)) {
|
if (!trimmedLine.match(/^\d+\.\s/)) {
|
||||||
hasAnyMarker = true;
|
|
||||||
} else {
|
|
||||||
allLinesHaveMarker = false;
|
allLinesHaveMarker = false;
|
||||||
}
|
}
|
||||||
} else if (before === "- [ ] ") {
|
} else if (before === "- [ ] ") {
|
||||||
// Для чекбокса
|
// Для чекбокса
|
||||||
if (trimmedLine.match(/^-\s+\[[ xX]\]\s/)) {
|
if (!trimmedLine.match(/^-\s+\[[ xX]\]\s/)) {
|
||||||
hasAnyMarker = true;
|
|
||||||
} else {
|
|
||||||
allLinesHaveMarker = false;
|
allLinesHaveMarker = false;
|
||||||
}
|
}
|
||||||
} else if (before === "> ") {
|
} else if (before === "> ") {
|
||||||
// Для цитаты
|
// Для цитаты
|
||||||
if (trimmedLine.startsWith("> ")) {
|
if (!trimmedLine.startsWith("> ")) {
|
||||||
hasAnyMarker = true;
|
|
||||||
} else {
|
|
||||||
allLinesHaveMarker = false;
|
allLinesHaveMarker = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -530,14 +520,12 @@ export const NoteEditor: React.FC<NoteEditorProps> = ({ onSave }) => {
|
|||||||
const lines = text.split("\n");
|
const lines = text.split("\n");
|
||||||
|
|
||||||
// Определяем текущую строку
|
// Определяем текущую строку
|
||||||
let currentLineIndex = 0;
|
|
||||||
let currentLineStart = 0;
|
let currentLineStart = 0;
|
||||||
let currentLine = "";
|
let currentLine = "";
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const lineLength = lines[i].length;
|
const lineLength = lines[i].length;
|
||||||
if (currentLineStart + lineLength >= start) {
|
if (currentLineStart + lineLength >= start) {
|
||||||
currentLineIndex = i;
|
|
||||||
currentLine = lines[i];
|
currentLine = lines[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -668,7 +656,6 @@ export const NoteEditor: React.FC<NoteEditorProps> = ({ onSave }) => {
|
|||||||
const lineHeight = parseInt(styles.lineHeight) || 20;
|
const lineHeight = parseInt(styles.lineHeight) || 20;
|
||||||
const paddingTop = parseInt(styles.paddingTop) || 0;
|
const paddingTop = parseInt(styles.paddingTop) || 0;
|
||||||
const paddingLeft = parseInt(styles.paddingLeft) || 0;
|
const paddingLeft = parseInt(styles.paddingLeft) || 0;
|
||||||
const fontSize = parseInt(styles.fontSize) || 14;
|
|
||||||
|
|
||||||
// Более точный расчет ширины символа
|
// Более точный расчет ширины символа
|
||||||
// Создаем временный элемент для измерения
|
// Создаем временный элемент для измерения
|
||||||
|
|||||||
@ -31,7 +31,6 @@ interface NoteItemProps {
|
|||||||
|
|
||||||
export const NoteItem: React.FC<NoteItemProps> = ({
|
export const NoteItem: React.FC<NoteItemProps> = ({
|
||||||
note,
|
note,
|
||||||
onDelete,
|
|
||||||
onPin,
|
onPin,
|
||||||
onArchive,
|
onArchive,
|
||||||
onReload,
|
onReload,
|
||||||
@ -140,20 +139,24 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
setLocalPreviewMode(false);
|
setLocalPreviewMode(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteExistingImage = (imageId: number) => {
|
const handleDeleteExistingImage = (imageId: number | string) => {
|
||||||
setDeletedImageIds([...deletedImageIds, imageId]);
|
const id = typeof imageId === 'number' ? imageId : Number(imageId);
|
||||||
|
setDeletedImageIds([...deletedImageIds, id]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteExistingFile = (fileId: number) => {
|
const handleDeleteExistingFile = (fileId: number | string) => {
|
||||||
setDeletedFileIds([...deletedFileIds, fileId]);
|
const id = typeof fileId === 'number' ? fileId : Number(fileId);
|
||||||
|
setDeletedFileIds([...deletedFileIds, id]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRestoreImage = (imageId: number) => {
|
const handleRestoreImage = (imageId: number | string) => {
|
||||||
setDeletedImageIds(deletedImageIds.filter((id) => id !== imageId));
|
const id = typeof imageId === 'number' ? imageId : Number(imageId);
|
||||||
|
setDeletedImageIds(deletedImageIds.filter((deletedId) => deletedId !== id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRestoreFile = (fileId: number) => {
|
const handleRestoreFile = (fileId: number | string) => {
|
||||||
setDeletedFileIds(deletedFileIds.filter((id) => id !== fileId));
|
const id = typeof fileId === 'number' ? fileId : Number(fileId);
|
||||||
|
setDeletedFileIds(deletedFileIds.filter((deletedId) => deletedId !== id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAiImprove = async () => {
|
const handleAiImprove = async () => {
|
||||||
@ -337,36 +340,27 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
|
|
||||||
// Определяем, есть ли уже такие маркеры на всех строках
|
// Определяем, есть ли уже такие маркеры на всех строках
|
||||||
let allLinesHaveMarker = true;
|
let allLinesHaveMarker = true;
|
||||||
let hasAnyMarker = false;
|
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmedLine = line.trimStart();
|
const trimmedLine = line.trimStart();
|
||||||
if (before === "- ") {
|
if (before === "- ") {
|
||||||
// Для маркированного списка проверяем различные варианты
|
// Для маркированного списка проверяем различные варианты
|
||||||
if (trimmedLine.match(/^[-*+]\s/)) {
|
if (!trimmedLine.match(/^[-*+]\s/)) {
|
||||||
hasAnyMarker = true;
|
|
||||||
} else {
|
|
||||||
allLinesHaveMarker = false;
|
allLinesHaveMarker = false;
|
||||||
}
|
}
|
||||||
} else if (before === "1. ") {
|
} else if (before === "1. ") {
|
||||||
// Для нумерованного списка
|
// Для нумерованного списка
|
||||||
if (trimmedLine.match(/^\d+\.\s/)) {
|
if (!trimmedLine.match(/^\d+\.\s/)) {
|
||||||
hasAnyMarker = true;
|
|
||||||
} else {
|
|
||||||
allLinesHaveMarker = false;
|
allLinesHaveMarker = false;
|
||||||
}
|
}
|
||||||
} else if (before === "- [ ] ") {
|
} else if (before === "- [ ] ") {
|
||||||
// Для чекбокса
|
// Для чекбокса
|
||||||
if (trimmedLine.match(/^-\s+\[[ xX]\]\s/)) {
|
if (!trimmedLine.match(/^-\s+\[[ xX]\]\s/)) {
|
||||||
hasAnyMarker = true;
|
|
||||||
} else {
|
|
||||||
allLinesHaveMarker = false;
|
allLinesHaveMarker = false;
|
||||||
}
|
}
|
||||||
} else if (before === "> ") {
|
} else if (before === "> ") {
|
||||||
// Для цитаты
|
// Для цитаты
|
||||||
if (trimmedLine.startsWith("> ")) {
|
if (!trimmedLine.startsWith("> ")) {
|
||||||
hasAnyMarker = true;
|
|
||||||
} else {
|
|
||||||
allLinesHaveMarker = false;
|
allLinesHaveMarker = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -628,7 +622,6 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
const lineHeight = parseInt(styles.lineHeight) || 20;
|
const lineHeight = parseInt(styles.lineHeight) || 20;
|
||||||
const paddingTop = parseInt(styles.paddingTop) || 0;
|
const paddingTop = parseInt(styles.paddingTop) || 0;
|
||||||
const paddingLeft = parseInt(styles.paddingLeft) || 0;
|
const paddingLeft = parseInt(styles.paddingLeft) || 0;
|
||||||
const fontSize = parseInt(styles.fontSize) || 14;
|
|
||||||
|
|
||||||
// Более точный расчет ширины символа
|
// Более точный расчет ширины символа
|
||||||
// Создаем временный элемент для измерения
|
// Создаем временный элемент для измерения
|
||||||
@ -751,14 +744,12 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
const lines = text.split("\n");
|
const lines = text.split("\n");
|
||||||
|
|
||||||
// Определяем текущую строку
|
// Определяем текущую строку
|
||||||
let currentLineIndex = 0;
|
|
||||||
let currentLineStart = 0;
|
let currentLineStart = 0;
|
||||||
let currentLine = "";
|
let currentLine = "";
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const lineLength = lines[i].length;
|
const lineLength = lines[i].length;
|
||||||
if (currentLineStart + lineLength >= start) {
|
if (currentLineStart + lineLength >= start) {
|
||||||
currentLineIndex = i;
|
|
||||||
currentLine = lines[i];
|
currentLine = lines[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1305,12 +1296,17 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="image-preview-list">
|
<div className="image-preview-list">
|
||||||
{note.images
|
{note.images
|
||||||
.filter((image) => !deletedImageIds.includes(image.id))
|
.filter((image) => {
|
||||||
|
const imageId = typeof image.id === 'number' ? image.id : Number(image.id);
|
||||||
|
return !deletedImageIds.includes(imageId);
|
||||||
|
})
|
||||||
.map((image) => {
|
.map((image) => {
|
||||||
|
const noteId = typeof note.id === 'number' ? note.id : Number(note.id);
|
||||||
|
const imageId = typeof image.id === 'number' ? image.id : Number(image.id);
|
||||||
const imageUrl = getImageUrl(
|
const imageUrl = getImageUrl(
|
||||||
image.file_path,
|
image.file_path,
|
||||||
note.id,
|
noteId,
|
||||||
image.id
|
imageId
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div key={image.id} className="image-preview-item">
|
<div key={image.id} className="image-preview-item">
|
||||||
@ -1344,12 +1340,17 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="image-preview-list">
|
<div className="image-preview-list">
|
||||||
{note.images
|
{note.images
|
||||||
.filter((image) => deletedImageIds.includes(image.id))
|
.filter((image) => {
|
||||||
|
const imageId = typeof image.id === 'number' ? image.id : Number(image.id);
|
||||||
|
return deletedImageIds.includes(imageId);
|
||||||
|
})
|
||||||
.map((image) => {
|
.map((image) => {
|
||||||
|
const noteId = typeof note.id === 'number' ? note.id : Number(note.id);
|
||||||
|
const imageId = typeof image.id === 'number' ? image.id : Number(image.id);
|
||||||
const imageUrl = getImageUrl(
|
const imageUrl = getImageUrl(
|
||||||
image.file_path,
|
image.file_path,
|
||||||
note.id,
|
noteId,
|
||||||
image.id
|
imageId
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div key={image.id} className="image-preview-item">
|
<div key={image.id} className="image-preview-item">
|
||||||
@ -1384,13 +1385,11 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="file-preview-list">
|
<div className="file-preview-list">
|
||||||
{note.files
|
{note.files
|
||||||
.filter((file) => !deletedFileIds.includes(file.id))
|
.filter((file) => {
|
||||||
|
const fileId = typeof file.id === 'number' ? file.id : Number(file.id);
|
||||||
|
return !deletedFileIds.includes(fileId);
|
||||||
|
})
|
||||||
.map((file) => {
|
.map((file) => {
|
||||||
const fileUrl = getFileUrl(
|
|
||||||
file.file_path,
|
|
||||||
note.id,
|
|
||||||
file.id
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<div key={file.id} className="file-preview-item">
|
<div key={file.id} className="file-preview-item">
|
||||||
<Icon
|
<Icon
|
||||||
@ -1430,7 +1429,10 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="file-preview-list">
|
<div className="file-preview-list">
|
||||||
{note.files
|
{note.files
|
||||||
.filter((file) => deletedFileIds.includes(file.id))
|
.filter((file) => {
|
||||||
|
const fileId = typeof file.id === 'number' ? file.id : Number(file.id);
|
||||||
|
return deletedFileIds.includes(fileId);
|
||||||
|
})
|
||||||
.map((file) => {
|
.map((file) => {
|
||||||
return (
|
return (
|
||||||
<div key={file.id} className="file-preview-item">
|
<div key={file.id} className="file-preview-item">
|
||||||
@ -1524,10 +1526,12 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
{note.images && note.images.length > 0 && (
|
{note.images && note.images.length > 0 && (
|
||||||
<div className="note-images-container">
|
<div className="note-images-container">
|
||||||
{note.images.map((image) => {
|
{note.images.map((image) => {
|
||||||
|
const noteId = typeof note.id === 'number' ? note.id : Number(note.id);
|
||||||
|
const imageId = typeof image.id === 'number' ? image.id : Number(image.id);
|
||||||
const imageUrl = getImageUrl(
|
const imageUrl = getImageUrl(
|
||||||
image.file_path,
|
image.file_path,
|
||||||
note.id,
|
noteId,
|
||||||
image.id
|
imageId
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div key={image.id} className="note-image-item">
|
<div key={image.id} className="note-image-item">
|
||||||
@ -1549,7 +1553,9 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
|||||||
{note.files && note.files.length > 0 && (
|
{note.files && note.files.length > 0 && (
|
||||||
<div className="note-files-container">
|
<div className="note-files-container">
|
||||||
{note.files.map((file) => {
|
{note.files.map((file) => {
|
||||||
const fileUrl = getFileUrl(file.file_path, note.id, file.id);
|
const noteId = typeof note.id === 'number' ? note.id : Number(note.id);
|
||||||
|
const fileId = typeof file.id === 'number' ? file.id : Number(file.id);
|
||||||
|
const fileUrl = getFileUrl(file.file_path, noteId, fileId);
|
||||||
return (
|
return (
|
||||||
<div key={file.id} className="note-file-item">
|
<div key={file.id} className="note-file-item">
|
||||||
<a
|
<a
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useImperativeHandle, forwardRef } from "react";
|
import { useEffect, useImperativeHandle, forwardRef } from "react";
|
||||||
import { NoteItem } from "./NoteItem";
|
import { NoteItem } from "./NoteItem";
|
||||||
import { useAppSelector, useAppDispatch } from "../../store/hooks";
|
import { useAppSelector, useAppDispatch } from "../../store/hooks";
|
||||||
import { offlineNotesApi } from "../../api/offlineNotesApi";
|
import { offlineNotesApi } from "../../api/offlineNotesApi";
|
||||||
@ -10,7 +10,7 @@ export interface NotesListRef {
|
|||||||
reloadNotes: () => void;
|
reloadNotes: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotesList = forwardRef<NotesListRef>((props, ref) => {
|
export const NotesList = forwardRef<NotesListRef>((_props, ref) => {
|
||||||
const notes = useAppSelector((state) => state.notes.notes);
|
const notes = useAppSelector((state) => state.notes.notes);
|
||||||
const userId = useAppSelector((state) => state.auth.userId);
|
const userId = useAppSelector((state) => state.auth.userId);
|
||||||
const searchQuery = useAppSelector((state) => state.notes.searchQuery);
|
const searchQuery = useAppSelector((state) => state.notes.searchQuery);
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { setSearchQuery } from "../../store/slices/notesSlice";
|
|||||||
export const SearchBar: React.FC = () => {
|
export const SearchBar: React.FC = () => {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Debounce для поиска
|
// Debounce для поиска
|
||||||
|
|||||||
@ -10,7 +10,8 @@ export const useNotification = () => {
|
|||||||
message: string,
|
message: string,
|
||||||
type: "info" | "success" | "error" | "warning" = "info"
|
type: "info" | "success" | "error" | "warning" = "info"
|
||||||
) => {
|
) => {
|
||||||
const id = dispatch(addNotification({ message, type })).payload.id;
|
const action = dispatch(addNotification({ message, type }));
|
||||||
|
const id = (action as any).payload?.id || `notification-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
dispatch(removeNotification(id));
|
dispatch(removeNotification(id));
|
||||||
}, 4000);
|
}, 4000);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { useAppSelector, useAppDispatch } from "../store/hooks";
|
import { useAppDispatch } from "../store/hooks";
|
||||||
import { userApi } from "../api/userApi";
|
import { userApi } from "../api/userApi";
|
||||||
import { authApi } from "../api/authApi";
|
import { authApi } from "../api/authApi";
|
||||||
import { clearAuth } from "../store/slices/authSlice";
|
import { clearAuth } from "../store/slices/authSlice";
|
||||||
@ -16,7 +16,6 @@ const ProfilePage: React.FC = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { showNotification } = useNotification();
|
const { showNotification } = useNotification();
|
||||||
const user = useAppSelector((state) => state.profile.user);
|
|
||||||
|
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { useAppSelector, useAppDispatch } from "../store/hooks";
|
import { useAppDispatch } from "../store/hooks";
|
||||||
import { userApi } from "../api/userApi";
|
import { userApi } from "../api/userApi";
|
||||||
import { authApi } from "../api/authApi";
|
import { authApi } from "../api/authApi";
|
||||||
import { notesApi, logsApi, Log } from "../api/notesApi";
|
import { notesApi, logsApi, Log } from "../api/notesApi";
|
||||||
@ -13,7 +13,6 @@ import { setAccentColor } from "../utils/colorUtils";
|
|||||||
import { useNotification } from "../hooks/useNotification";
|
import { useNotification } from "../hooks/useNotification";
|
||||||
import { Modal } from "../components/common/Modal";
|
import { Modal } from "../components/common/Modal";
|
||||||
import { ThemeToggle } from "../components/common/ThemeToggle";
|
import { ThemeToggle } from "../components/common/ThemeToggle";
|
||||||
import { formatDateFromTimestamp } from "../utils/dateFormat";
|
|
||||||
import { parseMarkdown } from "../utils/markdown";
|
import { parseMarkdown } from "../utils/markdown";
|
||||||
import { dbManager } from "../utils/indexedDB";
|
import { dbManager } from "../utils/indexedDB";
|
||||||
import { syncService } from "../services/syncService";
|
import { syncService } from "../services/syncService";
|
||||||
@ -25,8 +24,6 @@ const SettingsPage: React.FC = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { showNotification } = useNotification();
|
const { showNotification } = useNotification();
|
||||||
const user = useAppSelector((state) => state.profile.user);
|
|
||||||
const accentColor = useAppSelector((state) => state.ui.accentColor);
|
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<SettingsTab>(() => {
|
const [activeTab, setActiveTab] = useState<SettingsTab>(() => {
|
||||||
// Восстанавливаем активную вкладку из localStorage при инициализации
|
// Восстанавливаем активную вкладку из localStorage при инициализации
|
||||||
@ -162,11 +159,16 @@ const SettingsPage: React.FC = () => {
|
|||||||
|
|
||||||
const handleUpdateAppearance = async () => {
|
const handleUpdateAppearance = async () => {
|
||||||
try {
|
try {
|
||||||
await userApi.updateProfile({
|
const profileUpdate: any = {
|
||||||
accent_color: selectedAccentColor,
|
accent_color: selectedAccentColor,
|
||||||
show_edit_date: showEditDate,
|
};
|
||||||
colored_icons: coloredIcons,
|
if (showEditDate !== undefined) {
|
||||||
});
|
profileUpdate.show_edit_date = showEditDate;
|
||||||
|
}
|
||||||
|
if (coloredIcons !== undefined) {
|
||||||
|
profileUpdate.colored_icons = coloredIcons;
|
||||||
|
}
|
||||||
|
await userApi.updateProfile(profileUpdate);
|
||||||
dispatch(setAccentColorAction(selectedAccentColor));
|
dispatch(setAccentColorAction(selectedAccentColor));
|
||||||
setAccentColor(selectedAccentColor);
|
setAccentColor(selectedAccentColor);
|
||||||
await loadUserInfo();
|
await loadUserInfo();
|
||||||
@ -420,7 +422,7 @@ const SettingsPage: React.FC = () => {
|
|||||||
|
|
||||||
// Загружаем версию из IndexedDB
|
// Загружаем версию из IndexedDB
|
||||||
try {
|
try {
|
||||||
const userId = user?.id;
|
const userId = (await import("../store")).store.getState().auth.userId;
|
||||||
const localVer = userId
|
const localVer = userId
|
||||||
? await dbManager.getDataVersionByUserId(userId)
|
? await dbManager.getDataVersionByUserId(userId)
|
||||||
: await dbManager.getDataVersion();
|
: await dbManager.getDataVersion();
|
||||||
@ -837,14 +839,14 @@ const SettingsPage: React.FC = () => {
|
|||||||
<div className="archived-note-actions">
|
<div className="archived-note-actions">
|
||||||
<button
|
<button
|
||||||
className="btn-restore"
|
className="btn-restore"
|
||||||
onClick={() => handleRestoreNote(note.id)}
|
onClick={() => handleRestoreNote(typeof note.id === 'number' ? note.id : Number(note.id))}
|
||||||
title="Восстановить"
|
title="Восстановить"
|
||||||
>
|
>
|
||||||
<Icon icon="mdi:restore" /> Восстановить
|
<Icon icon="mdi:restore" /> Восстановить
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn-delete-permanent"
|
className="btn-delete-permanent"
|
||||||
onClick={() => handleDeletePermanent(note.id)}
|
onClick={() => handleDeletePermanent(typeof note.id === 'number' ? note.id : Number(note.id))}
|
||||||
title="Удалить навсегда"
|
title="Удалить навсегда"
|
||||||
>
|
>
|
||||||
<Icon icon="mdi:delete-forever" /> Удалить
|
<Icon icon="mdi:delete-forever" /> Удалить
|
||||||
|
|||||||
@ -5,13 +5,11 @@ import { Note, NoteImage, NoteFile } from '../types/note';
|
|||||||
import { store } from '../store/index';
|
import { store } from '../store/index';
|
||||||
import {
|
import {
|
||||||
setSyncStatus,
|
setSyncStatus,
|
||||||
removeNotification,
|
|
||||||
addNotification,
|
addNotification,
|
||||||
} from '../store/slices/uiSlice';
|
} from '../store/slices/uiSlice';
|
||||||
import {
|
import {
|
||||||
updateNote,
|
updateNote,
|
||||||
setPendingSyncCount,
|
setPendingSyncCount,
|
||||||
setOfflineMode,
|
|
||||||
} from '../store/slices/notesSlice';
|
} from '../store/slices/notesSlice';
|
||||||
import { SyncQueueItem } from '../types/note';
|
import { SyncQueueItem } from '../types/note';
|
||||||
|
|
||||||
@ -20,7 +18,7 @@ const RETRY_DELAY_MS = 5000;
|
|||||||
|
|
||||||
class SyncService {
|
class SyncService {
|
||||||
private isSyncing = false;
|
private isSyncing = false;
|
||||||
private syncTimer: NodeJS.Timeout | null = null;
|
private syncTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
private listeners: Array<() => void> = [];
|
private listeners: Array<() => void> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -444,7 +442,7 @@ class SyncService {
|
|||||||
*/
|
*/
|
||||||
private async updateImageReferences(
|
private async updateImageReferences(
|
||||||
localNote: Note,
|
localNote: Note,
|
||||||
serverNote: Note
|
_serverNote: Note
|
||||||
): Promise<NoteImage[]> {
|
): Promise<NoteImage[]> {
|
||||||
// Если нет изображений с base64, возвращаем как есть
|
// Если нет изображений с base64, возвращаем как есть
|
||||||
const hasBase64Images = localNote.images.some((img) => img.base64Data);
|
const hasBase64Images = localNote.images.some((img) => img.base64Data);
|
||||||
@ -461,7 +459,7 @@ class SyncService {
|
|||||||
*/
|
*/
|
||||||
private async updateFileReferences(
|
private async updateFileReferences(
|
||||||
localNote: Note,
|
localNote: Note,
|
||||||
serverNote: Note
|
_serverNote: Note
|
||||||
): Promise<NoteFile[]> {
|
): Promise<NoteFile[]> {
|
||||||
// Если нет файлов с base64, возвращаем как есть
|
// Если нет файлов с base64, возвращаем как есть
|
||||||
const hasBase64Files = localNote.files.some((file) => file.base64Data);
|
const hasBase64Files = localNote.files.some((file) => file.base64Data);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user