From f9ba1796dc8e52744f7692e3374bc52abfc89255 Mon Sep 17 00:00:00 2001 From: Fovway Date: Fri, 24 Oct 2025 08:05:40 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=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=B7=D0=B0=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=D0=BC=D0=B8=20=D0=B8=20=D0=BB=D0=BE=D0=B3=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B5=D0=B9?= =?UTF-8?q?=D1=81=D1=82=D0=B2=D0=B8=D0=B9=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Реализованы функции для закрепления и архивирования заметок, а также их восстановления. - Добавлены новые индексы в базу данных для улучшения производительности запросов. - Внедрено логирование действий пользователей, включая регистрацию, вход, создание, обновление и удаление заметок. - Обновлены интерфейсы для поддержки новых функций, включая кнопки для архивирования и закрепления заметок. - Оптимизированы стили и добавлены новые элементы управления для улучшения пользовательского опыта. --- public/app.js | 156 +++++++++++--- public/notes.html | 80 +++++--- public/settings.html | 157 ++++++++++++++ public/settings.js | 359 ++++++++++++++++++++++++++++++++ public/style.css | 474 ++++++++++++++++++++++++++++++++++++++++++- server.js | 442 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 1597 insertions(+), 71 deletions(-) create mode 100644 public/settings.html create mode 100644 public/settings.js diff --git a/public/app.js b/public/app.js index 9253f34..451b27d 100644 --- a/public/app.js +++ b/public/app.js @@ -6,8 +6,10 @@ const notesList = document.getElementById("notes-container"); // Получаем кнопки markdown const boldBtn = document.getElementById("boldBtn"); const italicBtn = document.getElementById("italicBtn"); +const strikethroughBtn = document.getElementById("strikethroughBtn"); const colorBtn = document.getElementById("colorBtn"); const headerBtn = document.getElementById("headerBtn"); +const headerDropdown = document.getElementById("headerDropdown"); const listBtn = document.getElementById("listBtn"); const numberedListBtn = document.getElementById("numberedListBtn"); const quoteBtn = document.getElementById("quoteBtn"); @@ -16,6 +18,9 @@ const linkBtn = document.getElementById("linkBtn"); const checkboxBtn = document.getElementById("checkboxBtn"); const imageBtn = document.getElementById("imageBtn"); +// Кнопка настроек +const settingsBtn = document.getElementById("settings-btn"); + // Элементы для загрузки изображений const imageInput = document.getElementById("imageInput"); const imagePreviewContainer = document.getElementById("imagePreviewContainer"); @@ -698,12 +703,34 @@ italicBtn.addEventListener("click", function () { insertMarkdown("*"); }); +strikethroughBtn.addEventListener("click", function () { + insertMarkdown("~~"); +}); + colorBtn.addEventListener("click", function () { insertColorTag(); }); -headerBtn.addEventListener("click", function () { - insertMarkdown("# "); +// Обработчик кнопки заголовка - открываем выпадающее меню +headerBtn.addEventListener("click", function (event) { + event.stopPropagation(); + headerDropdown.classList.toggle("show"); +}); + +// Обработчики для пунктов выпадающего меню +headerDropdown.querySelectorAll("button").forEach((btn) => { + btn.addEventListener("click", function (event) { + event.stopPropagation(); + const level = this.dataset.level; + const headerTag = "#".repeat(parseInt(level)) + " "; + insertMarkdown(headerTag); + headerDropdown.classList.remove("show"); + }); +}); + +// Закрытие выпадающего меню при клике вне его +document.addEventListener("click", function () { + headerDropdown.classList.remove("show"); }); listBtn.addEventListener("click", function () { @@ -1129,7 +1156,7 @@ function highlightSearchText(content, query) { return content.replace(regex, '$1'); } -// Настройка marked.js для поддержки чекбоксов +// Настройка marked.js для поддержки чекбоксов и strikethrough const renderer = new marked.Renderer(); // Переопределяем рендеринг списков, чтобы чекбоксы были кликабельными (без disabled) @@ -1147,7 +1174,7 @@ renderer.listitem = function (text, task, checked) { }; marked.setOptions({ - gfm: true, // GitHub Flavored Markdown + gfm: true, // GitHub Flavored Markdown (включает strikethrough) breaks: true, renderer: renderer, html: true, // Разрешить HTML теги @@ -1240,16 +1267,26 @@ async function renderNotes(notes) { dateDisplay = `${note.date} ${note.time}`; } + // Определяем класс для закрепленной заметки + const pinnedClass = note.is_pinned ? " note-pinned" : ""; + const pinIndicator = note.is_pinned + ? 'Закреплено' + : ""; + const noteHtml = ` -
+
- ${dateDisplay} -
Ред.
-
Удал.
+ ${dateDisplay}${pinIndicator} +
+
+ +
+
+ +
+
Ред.
+
Удал.
+
{ + btn.addEventListener("click", async function (event) { + const noteId = event.target.closest("#pinBtn").dataset.id; + try { + const response = await fetch(`/api/notes/${noteId}/pin`, { + method: "PUT", + }); + + if (!response.ok) { + throw new Error("Ошибка изменения закрепления"); + } + + const result = await response.json(); + + // Перезагружаем заметки + await loadNotes(true); + } catch (error) { + console.error("Ошибка:", error); + alert("Ошибка изменения закрепления"); + } + }); + }); + + // Обработчик архивирования + document.querySelectorAll("#archiveBtn").forEach((btn) => { + btn.addEventListener("click", async function (event) { + const noteId = event.target.closest("#archiveBtn").dataset.id; + if (confirm("Архивировать эту заметку? Её можно будет восстановить из настроек.")) { + try { + const response = await fetch(`/api/notes/${noteId}/archive`, { + method: "PUT", + }); + + if (!response.ok) { + throw new Error("Ошибка архивирования заметки"); + } + + // Перезагружаем заметки + await loadNotes(true); + } catch (error) { + console.error("Ошибка:", error); + alert("Ошибка архивирования заметки"); + } + } + }); + }); + // Обработчик удаления document.querySelectorAll("#deleteBtn").forEach((btn) => { btn.addEventListener("click", async function (event) { @@ -2257,6 +2342,13 @@ document.addEventListener("DOMContentLoaded", function () { // Добавляем обработчик для кнопки выхода setupLogoutHandler(); + + // Обработчик для кнопки настроек + if (settingsBtn) { + settingsBtn.addEventListener("click", function () { + window.location.href = "/settings"; + }); + } }); // Функция для настройки обработчика выхода @@ -2314,25 +2406,39 @@ async function loadUserInfo() { const response = await fetch("/api/user"); if (response.ok) { const user = await response.json(); - const usernameDisplay = document.getElementById("username-display"); const userAvatar = document.getElementById("user-avatar"); - const userAvatarContainer = document.getElementById( - "user-avatar-container" - ); + const userAvatarContainer = document.getElementById("user-avatar-container"); + const userAvatarPlaceholder = document.getElementById("user-avatar-placeholder"); - if (usernameDisplay) { - usernameDisplay.innerHTML = ` ${user.username}`; + // Показываем аватарку или плейсхолдер + if (user.avatar) { + if (userAvatar && userAvatarContainer) { + userAvatar.src = user.avatar; + userAvatarContainer.style.display = "block"; + if (userAvatarPlaceholder) { + userAvatarPlaceholder.style.display = "none"; + } + } + } else { + if (userAvatarPlaceholder) { + userAvatarPlaceholder.style.display = "flex"; + } + if (userAvatarContainer) { + userAvatarContainer.style.display = "none"; + } + } - // Делаем ник кликабельным для перехода в личный кабинет - usernameDisplay.style.cursor = "pointer"; - usernameDisplay.addEventListener("click", function () { + // Делаем аватарку и плейсхолдер кликабельными для перехода в профиль + if (userAvatarContainer) { + userAvatarContainer.style.cursor = "pointer"; + userAvatarContainer.addEventListener("click", function () { window.location.href = "/profile"; }); } - - // Аватарка скрыта на странице заметок - if (userAvatarContainer) { - userAvatarContainer.style.display = "none"; + if (userAvatarPlaceholder) { + userAvatarPlaceholder.addEventListener("click", function () { + window.location.href = "/profile"; + }); } // Применяем цветовой акцент пользователя (только если отличается от текущего) diff --git a/public/notes.html b/public/notes.html index e7e7c95..3b55d3e 100644 --- a/public/notes.html +++ b/public/notes.html @@ -222,33 +222,41 @@ style="display: none" >
-