✨ Добавлена функция удаления аккаунта пользователя
- Реализован новый API-эндпоинт для удаления аккаунта с подтверждением пароля. - Добавлено модальное окно для подтверждения удаления аккаунта на странице профиля. - Обновлены стили и логика для предпросмотра заметок с учетом текущей темы. - Улучшены обработчики событий для кнопки удаления аккаунта и модального окна.
This commit is contained in:
parent
59992e54dd
commit
155f4303d5
110
public/app.js
110
public/app.js
@ -1170,13 +1170,16 @@ function togglePreview() {
|
|||||||
const contentWithTags = makeTagsClickable(htmlContent);
|
const contentWithTags = makeTagsClickable(htmlContent);
|
||||||
notePreviewContent.innerHTML = contentWithTags;
|
notePreviewContent.innerHTML = contentWithTags;
|
||||||
|
|
||||||
|
// Применяем текущую тему к предпросмотру
|
||||||
|
applyThemeToPreview();
|
||||||
|
|
||||||
// Инициализируем lazy loading для изображений в превью
|
// Инициализируем lazy loading для изображений в превью
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initLazyLoading();
|
initLazyLoading();
|
||||||
}, 0);
|
}, 0);
|
||||||
} else {
|
} else {
|
||||||
notePreviewContent.innerHTML =
|
notePreviewContent.innerHTML =
|
||||||
'<p style="color: #999; font-style: italic;">Нет содержимого для предпросмотра</p>';
|
'<p style="color: var(--text-muted); font-style: italic;">Нет содержимого для предпросмотра</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Меняем иконку кнопки
|
// Меняем иконку кнопки
|
||||||
@ -1194,6 +1197,98 @@ function togglePreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция применения темы к предпросмотру
|
||||||
|
function applyThemeToPreview() {
|
||||||
|
if (!notePreviewContainer || notePreviewContainer.style.display === "none") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTheme = document.documentElement.getAttribute("data-theme");
|
||||||
|
|
||||||
|
// Применяем тему к контейнеру предпросмотра
|
||||||
|
if (currentTheme === "dark") {
|
||||||
|
notePreviewContainer.setAttribute("data-theme", "dark");
|
||||||
|
} else {
|
||||||
|
notePreviewContainer.removeAttribute("data-theme");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем стили для элементов внутри предпросмотра
|
||||||
|
const previewElements = notePreviewContent.querySelectorAll("*");
|
||||||
|
previewElements.forEach((element) => {
|
||||||
|
// Применяем тему к элементам кода
|
||||||
|
if (element.tagName === "CODE" || element.tagName === "PRE") {
|
||||||
|
if (currentTheme === "dark") {
|
||||||
|
element.style.backgroundColor = "var(--bg-quaternary)";
|
||||||
|
element.style.color = "#e6e6e6";
|
||||||
|
element.style.border = "1px solid var(--border-primary)";
|
||||||
|
} else {
|
||||||
|
element.style.backgroundColor = "var(--bg-quaternary)";
|
||||||
|
element.style.color = "var(--text-primary)";
|
||||||
|
element.style.border = "1px solid var(--border-primary)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем тему к цитатам
|
||||||
|
if (element.tagName === "BLOCKQUOTE") {
|
||||||
|
if (currentTheme === "dark") {
|
||||||
|
element.style.backgroundColor = "var(--bg-tertiary)";
|
||||||
|
element.style.borderLeftColor = "var(--accent-color, #4a9eff)";
|
||||||
|
element.style.color = "var(--text-secondary)";
|
||||||
|
} else {
|
||||||
|
element.style.backgroundColor = "var(--bg-tertiary)";
|
||||||
|
element.style.borderLeftColor = "var(--accent-color, #007bff)";
|
||||||
|
element.style.color = "var(--text-secondary)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция применения темы к предпросмотру в режиме редактирования
|
||||||
|
function applyThemeToEditPreview(editPreviewContainer, editPreviewContent) {
|
||||||
|
if (!editPreviewContainer || editPreviewContainer.style.display === "none") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTheme = document.documentElement.getAttribute("data-theme");
|
||||||
|
|
||||||
|
// Применяем тему к контейнеру предпросмотра редактирования
|
||||||
|
if (currentTheme === "dark") {
|
||||||
|
editPreviewContainer.setAttribute("data-theme", "dark");
|
||||||
|
} else {
|
||||||
|
editPreviewContainer.removeAttribute("data-theme");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем стили для элементов внутри предпросмотра редактирования
|
||||||
|
const previewElements = editPreviewContent.querySelectorAll("*");
|
||||||
|
previewElements.forEach((element) => {
|
||||||
|
// Применяем тему к элементам кода
|
||||||
|
if (element.tagName === "CODE" || element.tagName === "PRE") {
|
||||||
|
if (currentTheme === "dark") {
|
||||||
|
element.style.backgroundColor = "var(--bg-quaternary)";
|
||||||
|
element.style.color = "#e6e6e6";
|
||||||
|
element.style.border = "1px solid var(--border-primary)";
|
||||||
|
} else {
|
||||||
|
element.style.backgroundColor = "var(--bg-quaternary)";
|
||||||
|
element.style.color = "var(--text-primary)";
|
||||||
|
element.style.border = "1px solid var(--border-primary)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем тему к цитатам
|
||||||
|
if (element.tagName === "BLOCKQUOTE") {
|
||||||
|
if (currentTheme === "dark") {
|
||||||
|
element.style.backgroundColor = "var(--bg-tertiary)";
|
||||||
|
element.style.borderLeftColor = "var(--accent-color, #4a9eff)";
|
||||||
|
element.style.color = "var(--text-secondary)";
|
||||||
|
} else {
|
||||||
|
element.style.backgroundColor = "var(--bg-tertiary)";
|
||||||
|
element.style.borderLeftColor = "var(--accent-color, #007bff)";
|
||||||
|
element.style.color = "var(--text-secondary)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Обработчик выбора файлов
|
// Обработчик выбора файлов
|
||||||
imageInput.addEventListener("change", function (event) {
|
imageInput.addEventListener("change", function (event) {
|
||||||
const files = Array.from(event.target.files);
|
const files = Array.from(event.target.files);
|
||||||
@ -2566,13 +2661,19 @@ function addNoteEventListeners() {
|
|||||||
const contentWithTags = makeTagsClickable(htmlContent);
|
const contentWithTags = makeTagsClickable(htmlContent);
|
||||||
editPreviewContent.innerHTML = contentWithTags;
|
editPreviewContent.innerHTML = contentWithTags;
|
||||||
|
|
||||||
|
// Применяем текущую тему к предпросмотру редактирования
|
||||||
|
applyThemeToEditPreview(
|
||||||
|
editPreviewContainer,
|
||||||
|
editPreviewContent
|
||||||
|
);
|
||||||
|
|
||||||
// Инициализируем lazy loading для изображений в превью
|
// Инициализируем lazy loading для изображений в превью
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initLazyLoading();
|
initLazyLoading();
|
||||||
}, 0);
|
}, 0);
|
||||||
} else {
|
} else {
|
||||||
editPreviewContent.innerHTML =
|
editPreviewContent.innerHTML =
|
||||||
'<p style="color: #999; font-style: italic;">Нет содержимого для предпросмотра</p>';
|
'<p style="color: var(--text-muted); font-style: italic;">Нет содержимого для предпросмотра</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Меняем иконку кнопки
|
// Меняем иконку кнопки
|
||||||
@ -4068,6 +4169,11 @@ function applyTheme(theme) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Применяем тему к предпросмотру, если он открыт
|
||||||
|
if (isPreviewMode) {
|
||||||
|
applyThemeToPreview();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализируем переключатель темы при загрузке страницы
|
// Инициализируем переключатель темы при загрузке страницы
|
||||||
|
|||||||
@ -189,6 +189,61 @@
|
|||||||
<button id="changePasswordBtn" class="btnSave">
|
<button id="changePasswordBtn" class="btnSave">
|
||||||
Изменить пароль
|
Изменить пароль
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<hr class="separator" />
|
||||||
|
|
||||||
|
<button id="deleteAccountBtn" class="btn-danger">
|
||||||
|
<span class="iconify" data-icon="mdi:account-remove"></span>
|
||||||
|
Удалить аккаунт
|
||||||
|
</button>
|
||||||
|
<p style="color: #666; font-size: 14px; margin-bottom: 15px">
|
||||||
|
Удаление аккаунта - это необратимое действие. Все ваши заметки,
|
||||||
|
изображения и данные будут удалены навсегда.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Модальное окно подтверждения удаления аккаунта -->
|
||||||
|
<div id="deleteAccountModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 style="color: #dc3545">Удаление аккаунта</h3>
|
||||||
|
<span class="modal-close" id="deleteAccountModalClose">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p style="color: #dc3545; font-weight: bold; margin-bottom: 15px">
|
||||||
|
⚠️ ВНИМАНИЕ: Это действие нельзя отменить!
|
||||||
|
</p>
|
||||||
|
<p style="margin-bottom: 20px">
|
||||||
|
Вы действительно хотите удалить свой аккаунт? Все ваши заметки,
|
||||||
|
изображения, настройки и данные будут удалены навсегда.
|
||||||
|
</p>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label
|
||||||
|
for="deleteAccountPassword"
|
||||||
|
style="display: block; margin-bottom: 5px; font-weight: bold"
|
||||||
|
>
|
||||||
|
Введите пароль для подтверждения:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="deleteAccountPassword"
|
||||||
|
placeholder="Пароль от аккаунта"
|
||||||
|
class="modal-password-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button
|
||||||
|
id="confirmDeleteAccount"
|
||||||
|
class="btn-danger"
|
||||||
|
style="margin-right: 10px"
|
||||||
|
>
|
||||||
|
<span class="iconify" data-icon="mdi:account-remove"></span> Удалить
|
||||||
|
аккаунт
|
||||||
|
</button>
|
||||||
|
<button id="cancelDeleteAccount" class="btn-secondary">Отмена</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -688,6 +688,99 @@ function setupLogoutHandler() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для инициализации обработчика удаления аккаунта
|
||||||
|
function initDeleteAccountHandler() {
|
||||||
|
const deleteAccountBtn = document.getElementById("deleteAccountBtn");
|
||||||
|
const modal = document.getElementById("deleteAccountModal");
|
||||||
|
const closeBtn = document.getElementById("deleteAccountModalClose");
|
||||||
|
const cancelBtn = document.getElementById("cancelDeleteAccount");
|
||||||
|
const confirmBtn = document.getElementById("confirmDeleteAccount");
|
||||||
|
const passwordInput = document.getElementById("deleteAccountPassword");
|
||||||
|
|
||||||
|
// Открытие модального окна
|
||||||
|
deleteAccountBtn.addEventListener("click", () => {
|
||||||
|
// Очищаем поле пароля и открываем модальное окно
|
||||||
|
passwordInput.value = "";
|
||||||
|
modal.style.display = "block";
|
||||||
|
passwordInput.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Закрытие модального окна
|
||||||
|
function closeModal() {
|
||||||
|
modal.style.display = "none";
|
||||||
|
passwordInput.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
closeBtn.addEventListener("click", closeModal);
|
||||||
|
cancelBtn.addEventListener("click", closeModal);
|
||||||
|
|
||||||
|
// Закрытие при клике вне модального окна
|
||||||
|
window.addEventListener("click", (e) => {
|
||||||
|
if (e.target === modal) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Подтверждение удаления
|
||||||
|
confirmBtn.addEventListener("click", async () => {
|
||||||
|
const password = passwordInput.value.trim();
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
showNotification("Введите пароль", "warning");
|
||||||
|
passwordInput.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Блокируем кнопку во время выполнения
|
||||||
|
confirmBtn.disabled = true;
|
||||||
|
confirmBtn.innerHTML =
|
||||||
|
'<span class="iconify" data-icon="mdi:loading"></span> Удаление...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/user/delete-account", {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || "Ошибка удаления аккаунта");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Успешное удаление
|
||||||
|
showNotification("Аккаунт успешно удален", "success");
|
||||||
|
|
||||||
|
// Очищаем localStorage
|
||||||
|
localStorage.removeItem("isAuthenticated");
|
||||||
|
localStorage.removeItem("username");
|
||||||
|
|
||||||
|
// Перенаправляем на главную страницу
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "/";
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка:", error);
|
||||||
|
showNotification(error.message || "Ошибка удаления аккаунта", "error");
|
||||||
|
} finally {
|
||||||
|
// Разблокируем кнопку
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
confirmBtn.innerHTML =
|
||||||
|
'<span class="iconify" data-icon="mdi:account-remove"></span> Удалить аккаунт';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработка Enter в поле пароля
|
||||||
|
passwordInput.addEventListener("keypress", (e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
confirmBtn.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Загружаем профиль при загрузке страницы
|
// Загружаем профиль при загрузке страницы
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Проверяем аутентификацию при загрузке страницы
|
// Проверяем аутентификацию при загрузке страницы
|
||||||
@ -699,4 +792,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
|
|
||||||
// Добавляем обработчик для кнопки выхода
|
// Добавляем обработчик для кнопки выхода
|
||||||
setupLogoutHandler();
|
setupLogoutHandler();
|
||||||
|
|
||||||
|
// Инициализируем обработчик удаления аккаунта
|
||||||
|
initDeleteAccountHandler();
|
||||||
});
|
});
|
||||||
|
|||||||
292
public/style.css
292
public/style.css
@ -1957,12 +1957,13 @@ textarea:focus {
|
|||||||
.note-preview-container {
|
.note-preview-container {
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background: #f8f9fa;
|
background: var(--bg-tertiary);
|
||||||
border: 2px solid #007bff;
|
border: 2px solid var(--accent-color, #007bff);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-preview-header {
|
.note-preview-header {
|
||||||
@ -1971,17 +1972,20 @@ textarea:focus {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
border-bottom: 2px solid #dee2e6;
|
border-bottom: 2px solid var(--border-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #007bff;
|
color: var(--accent-color, #007bff);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
transition: color 0.3s ease, border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-preview-content {
|
.note-preview-content {
|
||||||
background: white;
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-preview-content h1,
|
.note-preview-content h1,
|
||||||
@ -1992,32 +1996,63 @@ textarea:focus {
|
|||||||
.note-preview-content h6 {
|
.note-preview-content h6 {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-preview-content p {
|
.note-preview-content p {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-preview-content ul,
|
.note-preview-content ul,
|
||||||
.note-preview-content ol {
|
.note-preview-content ol {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content li {
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content a {
|
||||||
|
color: var(--accent-color, #007bff);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-preview-content code {
|
.note-preview-content code {
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-quaternary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-family: "Courier New", monospace;
|
font-family: "Courier New", monospace;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-preview-content pre {
|
.note-preview-content pre {
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-quaternary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease,
|
||||||
|
border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content pre code {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-preview-content blockquote {
|
.note-preview-content blockquote {
|
||||||
@ -2028,6 +2063,13 @@ textarea:focus {
|
|||||||
background-color: var(--bg-tertiary);
|
background-color: var(--bg-tertiary);
|
||||||
padding: 8px 15px;
|
padding: 8px 15px;
|
||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
|
transition: color 0.3s ease, background-color 0.3s ease,
|
||||||
|
border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content blockquote p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-preview-content img {
|
.note-preview-content img {
|
||||||
@ -2035,6 +2077,192 @@ textarea:focus {
|
|||||||
height: auto;
|
height: auto;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
box-shadow: 0 2px 4px var(--shadow-light);
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 10px 0;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 1px 3px var(--shadow-light);
|
||||||
|
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content th,
|
||||||
|
.note-preview-content td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--border-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: color 0.3s ease, border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content th {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content tr:hover {
|
||||||
|
background: var(--bg-quaternary);
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content hr {
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--border-primary);
|
||||||
|
margin: 20px 0;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Специальные стили для темной темы в предпросмотре */
|
||||||
|
[data-theme="dark"] .note-preview-container {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-color: var(--accent-color, #4a9eff);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-header {
|
||||||
|
color: var(--accent-color, #4a9eff);
|
||||||
|
border-bottom-color: var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content code {
|
||||||
|
background: var(--bg-quaternary);
|
||||||
|
color: #e6e6e6;
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content pre {
|
||||||
|
background: var(--bg-quaternary);
|
||||||
|
color: #e6e6e6;
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content pre code {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content blockquote {
|
||||||
|
background-color: var(--bg-tertiary);
|
||||||
|
border-left-color: var(--accent-color, #4a9eff);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content table {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
box-shadow: 0 1px 3px var(--shadow-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content th {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content th,
|
||||||
|
[data-theme="dark"] .note-preview-content td {
|
||||||
|
border-bottom-color: var(--border-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content tr:hover {
|
||||||
|
background: var(--bg-quaternary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .note-preview-content img {
|
||||||
|
box-shadow: 0 2px 4px var(--shadow-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стили для чекбоксов в предпросмотре */
|
||||||
|
.note-preview-content input[type="checkbox"] {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
accent-color: var(--accent-color, #007bff);
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стили для элементов списка с чекбоксами в предпросмотре */
|
||||||
|
.note-preview-content .task-list-item {
|
||||||
|
list-style-type: none;
|
||||||
|
margin-left: -20px;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content .task-list-item:has(input[type="checkbox"]:checked) {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content .task-list-item input[type="checkbox"]:checked ~ * {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content .task-list-item:hover {
|
||||||
|
background-color: rgba(0, 123, 255, 0.05);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding-left: 4px;
|
||||||
|
margin-left: -24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Альтернативный вариант для перечеркивания всего текста в элементе */
|
||||||
|
.note-preview-content input[type="checkbox"]:checked {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content input[type="checkbox"]:checked + * {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Если marked.js не добавляет класс task-list-item, используем :has селектор */
|
||||||
|
.note-preview-content li:has(input[type="checkbox"]) {
|
||||||
|
list-style-type: none;
|
||||||
|
margin-left: -20px;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content li:has(input[type="checkbox"]:checked) {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content
|
||||||
|
li:has(input[type="checkbox"])
|
||||||
|
input[type="checkbox"]:checked {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content li:has(input[type="checkbox"]:checked) label,
|
||||||
|
.note-preview-content li:has(input[type="checkbox"]:checked) span,
|
||||||
|
.note-preview-content li:has(input[type="checkbox"]:checked) p {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content li:has(input[type="checkbox"]):hover {
|
||||||
|
background-color: rgba(0, 123, 255, 0.05);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding-left: 4px;
|
||||||
|
margin-left: -24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear-images-btn {
|
.clear-images-btn {
|
||||||
@ -2621,6 +2849,56 @@ textarea:focus {
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стили для опасной зоны */
|
||||||
|
.danger-zone {
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border: 2px solid #dc3545;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-zone::before {
|
||||||
|
content: "⚠️";
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: 20px;
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-zone h3 {
|
||||||
|
color: #dc3545;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-zone p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-zone .btn-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
border-color: #dc3545;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-zone .btn-danger:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
border-color: #bd2130;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
background-color: var(--bg-tertiary);
|
background-color: var(--bg-tertiary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
|||||||
133
server.js
133
server.js
@ -2109,6 +2109,139 @@ app.post("/logout", (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// API для удаления аккаунта
|
||||||
|
app.delete("/api/user/delete-account", requireApiAuth, async (req, res) => {
|
||||||
|
const { password } = req.body;
|
||||||
|
const userId = req.session.userId;
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
return res.status(400).json({ error: "Пароль обязателен" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Получаем пользователя и проверяем пароль
|
||||||
|
const getUserSql = "SELECT id, password FROM users WHERE id = ?";
|
||||||
|
db.get(getUserSql, [userId], async (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка получения пользователя:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ error: "Пользователь не найден" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем пароль
|
||||||
|
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||||
|
if (!isPasswordValid) {
|
||||||
|
return res.status(401).json({ error: "Неверный пароль" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем аватарку пользователя перед удалением
|
||||||
|
const getAvatarSql = "SELECT avatar FROM users WHERE id = ?";
|
||||||
|
db.get(getAvatarSql, [userId], (err, userData) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка получения аватарки:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка получения аватарки" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаляем файл аватарки, если он существует
|
||||||
|
if (userData && userData.avatar) {
|
||||||
|
const avatarPath = path.join(
|
||||||
|
__dirname,
|
||||||
|
"public",
|
||||||
|
"uploads",
|
||||||
|
userData.avatar
|
||||||
|
);
|
||||||
|
fs.unlink(avatarPath, (err) => {
|
||||||
|
if (err && err.code !== "ENOENT") {
|
||||||
|
console.error("Ошибка удаления файла аватарки:", err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Начинаем транзакцию для удаления всех данных пользователя
|
||||||
|
db.serialize(() => {
|
||||||
|
db.run("BEGIN TRANSACTION");
|
||||||
|
|
||||||
|
// Удаляем все изображения заметок пользователя (через JOIN с notes)
|
||||||
|
const deleteImagesSql = `
|
||||||
|
DELETE FROM note_images
|
||||||
|
WHERE note_id IN (SELECT id FROM notes WHERE user_id = ?)
|
||||||
|
`;
|
||||||
|
db.run(deleteImagesSql, [userId], (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка удаления изображений:", err.message);
|
||||||
|
db.run("ROLLBACK");
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ error: "Ошибка удаления изображений" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Удаляем все заметки пользователя (CASCADE удалит связанные изображения)
|
||||||
|
db.run("DELETE FROM notes WHERE user_id = ?", [userId], (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка удаления заметок:", err.message);
|
||||||
|
db.run("ROLLBACK");
|
||||||
|
return res.status(500).json({ error: "Ошибка удаления заметок" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Удаляем логи пользователя
|
||||||
|
db.run(
|
||||||
|
"DELETE FROM action_logs WHERE user_id = ?",
|
||||||
|
[userId],
|
||||||
|
(err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка удаления логов:", err.message);
|
||||||
|
db.run("ROLLBACK");
|
||||||
|
return res.status(500).json({ error: "Ошибка удаления логов" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Удаляем пользователя из базы данных
|
||||||
|
db.run("DELETE FROM users WHERE id = ?", [userId], (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка удаления пользователя:", err.message);
|
||||||
|
db.run("ROLLBACK");
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ error: "Ошибка удаления пользователя" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Подтверждаем транзакцию
|
||||||
|
db.run("COMMIT", (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка подтверждения транзакции:", err.message);
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ error: "Ошибка подтверждения транзакции" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уничтожаем сессию
|
||||||
|
req.session.destroy((err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка уничтожения сессии:", err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
message: "Аккаунт успешно удален",
|
||||||
|
success: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка удаления аккаунта:", error);
|
||||||
|
res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Запуск сервера
|
// Запуск сервера
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`🚀 Сервер запущен на порту ${PORT}`);
|
console.log(`🚀 Сервер запущен на порту ${PORT}`);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user