From 1479205261985552d23973d775dd8f21e2510b9b Mon Sep 17 00:00:00 2001 From: Fovway Date: Tue, 28 Oct 2025 06:22:37 +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=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B6=D0=BA=D0=B0=20=D1=81=D0=BF=D0=BE=D0=B9=D0=BB=D0=B5=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B8=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20AI=20=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Реализована возможность вставки спойлеров в заметки с помощью нового интерфейса и логики обработки. - Добавлен переключатель для включения/выключения помощи ИИ в настройках пользователя, с проверкой заполненности обязательных полей. - Обновлены API для получения и сохранения настроек AI, включая новую колонку `ai_enabled` в таблице пользователей. - Улучшены стили и обработчики событий для новых элементов интерфейса, включая спойлеры и переключатель AI. --- public/app.js | 133 +++++++++++++++++++++++++++- public/notes.html | 3 + public/settings.html | 19 ++++ public/settings.js | 151 +++++++++++++++++++++++++++++++ public/style.css | 205 +++++++++++++++++++++++++++++++++++++++++++ server.js | 90 ++++++++++++++++--- 6 files changed, 586 insertions(+), 15 deletions(-) diff --git a/public/app.js b/public/app.js index a5c85e1..457effc 100644 --- a/public/app.js +++ b/public/app.js @@ -305,6 +305,7 @@ const codeBtn = document.getElementById("codeBtn"); const linkBtn = document.getElementById("linkBtn"); const checkboxBtn = document.getElementById("checkboxBtn"); const imageBtn = document.getElementById("imageBtn"); +const spoilerBtn = document.getElementById("spoilerBtn"); const previewBtn = document.getElementById("previewBtn"); const aiImproveBtn = document.getElementById("aiImproveBtn"); @@ -669,6 +670,33 @@ function insertColorMarkdown(color) { } // Функция для вставки markdown +function insertSpoiler() { + const start = noteInput.selectionStart; + const end = noteInput.selectionEnd; + const text = noteInput.value; + + const before = text.substring(0, start); + const selected = text.substring(start, end); + const after = text.substring(end); + + let newText; + let newCursorPos; + + if (selected) { + // Если есть выделенный текст, оборачиваем его в спойлер + newText = before + "||" + selected + "||" + after; + newCursorPos = start + selected.length + 4; // После выделенного текста + } else { + // Если нет выделенного текста, вставляем пустой спойлер + newText = before + "||скрытый текст||" + after; + newCursorPos = start + 2; // Внутри спойлера для редактирования + } + + noteInput.value = newText; + noteInput.setSelectionRange(newCursorPos, newCursorPos); + noteInput.focus(); +} + function insertMarkdown(tag) { const start = noteInput.selectionStart; const end = noteInput.selectionEnd; @@ -1084,6 +1112,10 @@ checkboxBtn.addEventListener("click", function () { insertMarkdown("- [ ] "); }); +spoilerBtn.addEventListener("click", function () { + insertSpoiler(); +}); + // Обработчик для кнопки загрузки изображений imageBtn.addEventListener("click", function (event) { event.preventDefault(); @@ -1721,6 +1753,32 @@ renderer.listitem = function (text, task, checked) { return originalListItem(text, task, checked); }; +// Кастомное расширение для скрытого текста (спойлеров) +const spoilerExtension = { + name: "spoiler", + level: "inline", + start(src) { + return src.match(/\|\|/) ? src.indexOf("||") : -1; + }, + tokenizer(src, tokens) { + const rule = /^\|\|(.*?)\|\|/; + const match = rule.exec(src); + if (match) { + return { + type: "spoiler", + raw: match[0], + text: match[1].trim(), + }; + } + }, + renderer(token) { + return `${token.text}`; + }, +}; + +// Регистрируем расширение через marked.use() +marked.use({ extensions: [spoilerExtension] }); + marked.setOptions({ gfm: true, // GitHub Flavored Markdown (включает strikethrough) breaks: true, @@ -1871,6 +1929,9 @@ async function renderNotes(notes) { // Добавляем обработчики для чекбоксов в заметках addCheckboxEventListeners(); + // Добавляем обработчики для спойлеров + addSpoilerEventListeners(); + // Обрабатываем длинные заметки handleLongNotes(); @@ -2390,6 +2451,12 @@ function addNoteEventListeners() { ' Помощь ИИ'; aiImproveEditBtn.title = "Улучшить или создать текст через ИИ"; + // Проверяем настройку AI и скрываем кнопку если отключено + const aiEnabled = localStorage.getItem("ai_enabled"); + if (aiEnabled !== "1") { + aiImproveEditBtn.style.display = "none"; + } + // Кнопка сохранить const saveEditBtn = document.createElement("button"); saveEditBtn.textContent = "Сохранить"; @@ -2969,6 +3036,24 @@ function addCheckboxEventListeners() { }); } +function addSpoilerEventListeners() { + document.querySelectorAll(".spoiler").forEach((spoiler) => { + // Проверяем, не добавлен ли уже обработчик + if (spoiler._clickHandler) { + return; // Пропускаем, если обработчик уже добавлен + } + + // Создаем новый обработчик + spoiler._clickHandler = function (event) { + event.stopPropagation(); + this.classList.toggle("revealed"); + console.log("Спойлер кликнут:", this.textContent); + }; + + spoiler.addEventListener("click", spoiler._clickHandler); + }); +} + // Функция сохранения заметки (вынесена отдельно для повторного использования) async function saveNote() { if (noteInput.value.trim() !== "" || selectedImages.length > 0) { @@ -4176,5 +4261,51 @@ function applyTheme(theme) { } } +// Функция для проверки и применения видимости кнопок AI +async function updateAiButtonsVisibility() { + // Проверяем localStorage сначала для быстрого ответа + let aiEnabled = localStorage.getItem("ai_enabled"); + + // Если нет в localStorage, загружаем с сервера + if (aiEnabled === null) { + try { + const response = await fetch("/api/user/ai-settings"); + if (response.ok) { + const settings = await response.json(); + aiEnabled = settings.ai_enabled ? "1" : "0"; + localStorage.setItem("ai_enabled", aiEnabled); + } else { + aiEnabled = "0"; // По умолчанию выключено + } + } catch (error) { + console.error("Ошибка загрузки настроек AI:", error); + aiEnabled = "0"; // По умолчанию выключено + } + } + + const isEnabled = aiEnabled === "1"; + + // Показываем/скрываем кнопку AI в основном редакторе + const mainAiBtn = document.getElementById("aiImproveBtn"); + if (mainAiBtn) { + mainAiBtn.style.display = isEnabled ? "" : "none"; + } + + // Показываем/скрываем кнопки AI в редакторах заметок + document.querySelectorAll(".btnAI").forEach((btn) => { + btn.style.display = isEnabled ? "" : "none"; + }); +} + // Инициализируем переключатель темы при загрузке страницы -document.addEventListener("DOMContentLoaded", initThemeToggle); +document.addEventListener("DOMContentLoaded", () => { + initThemeToggle(); + updateAiButtonsVisibility(); +}); + +// Обновляем видимость кнопок AI когда пользователь возвращается на страницу +document.addEventListener("visibilitychange", () => { + if (!document.hidden) { + updateAiButtonsVisibility(); + } +}); diff --git a/public/notes.html b/public/notes.html index 0fbc1f2..3b26c27 100644 --- a/public/notes.html +++ b/public/notes.html @@ -295,6 +295,9 @@ +