From 493e1a57be79f8ba2319b4768d91a3f0cf38bf58 Mon Sep 17 00:00:00 2001 From: Fovway Date: Sat, 18 Oct 2025 15:42:03 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BE=D1=82=D0=BC=D0=B5=D1=82=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D0=BA=D0=B0=D0=BB=D0=B5=D0=BD=D0=B4=D0=B0?= =?UTF-8?q?=D1=80=D0=B5=20=D0=B8=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=BF=D0=BE=20=D0=B4=D0=B0=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлены зеленые индикаторы на днях с заметками в календаре - Реализована фильтрация заметок по выбранной дате при клике на день - Добавлен индикатор активного фильтра с кнопкой сброса - Исправлено выравнивание элементов заголовка при появлении фильтра - Добавлена навигация по месяцам с сохранением фильтра - Повторный клик на день снимает фильтр Изменения: - public/app.js: логика фильтрации и отображения индикаторов - public/notes.html: структура заголовка с индикатором фильтра - public/style.css: стили для индикаторов и исправление выравнивания --- public/app.js | 249 +++++++++++++++++++++++++++++++++++++++++++++- public/notes.html | 153 ++++++++++++++++------------ public/style.css | 174 +++++++++++++++++++++++++++++++- 3 files changed, 506 insertions(+), 70 deletions(-) diff --git a/public/app.js b/public/app.js index f2e0601..826a1e2 100644 --- a/public/app.js +++ b/public/app.js @@ -1,7 +1,7 @@ // DOM элементы const noteInput = document.getElementById("noteInput"); const saveBtn = document.getElementById("saveBtn"); -const notesList = document.getElementById("notesList"); +const notesList = document.getElementById("notes-container"); // Получаем кнопки markdown const boldBtn = document.getElementById("boldBtn"); @@ -12,6 +12,10 @@ const quoteBtn = document.getElementById("quoteBtn"); const codeBtn = document.getElementById("codeBtn"); const linkBtn = document.getElementById("linkBtn"); +// Глобальные переменные для заметок и фильтрации +let allNotes = []; +let selectedDateFilter = null; + // Функция для получения текущей даты и времени function getFormattedDateTime() { let now = new Date(); @@ -194,7 +198,9 @@ async function loadNotes() { throw new Error("Ошибка загрузки заметок"); } const notes = await response.json(); + allNotes = notes; // Сохраняем все заметки в глобальную переменную renderNotes(notes); + renderCalendar(); // Обновляем календарь после загрузки заметок } catch (error) { console.error("Ошибка:", error); notesList.innerHTML = "

Ошибка загрузки заметок

"; @@ -205,8 +211,25 @@ async function loadNotes() { function renderNotes(notes) { notesList.innerHTML = ""; + // Фильтруем заметки, если выбрана дата + let notesToDisplay = notes; + if (selectedDateFilter) { + notesToDisplay = notes.filter((note) => note.date === selectedDateFilter); + } + + // Если нет заметок для отображения + if (notesToDisplay.length === 0) { + if (selectedDateFilter) { + notesList.innerHTML = `

Нет заметок за выбранную дату (${selectedDateFilter})

`; + } else { + notesList.innerHTML = + '

Заметок пока нет

'; + } + return; + } + // Итерируемся по заметкам в обычном порядке, чтобы новые были сверху - notes.forEach(function (note) { + notesToDisplay.forEach(function (note) { const noteHtml = `
@@ -400,6 +423,7 @@ noteInput.addEventListener("keydown", function (event) { document.addEventListener("DOMContentLoaded", function () { loadUserInfo(); loadNotes(); + updateFilterIndicator(); }); // Функция для загрузки информации о пользователе @@ -440,3 +464,224 @@ async function loadUserInfo() { console.error("Ошибка загрузки информации о пользователе:", error); } } + +// Календарь +let currentDate = new Date(); + +// Функция для отображения календаря +function renderCalendar() { + const calendarDays = document.getElementById("calendarDays"); + const monthYear = document.getElementById("monthYear"); + + // Проверяем, существуют ли элементы календаря + if (!calendarDays || !monthYear) return; + + const year = currentDate.getFullYear(); + const month = currentDate.getMonth(); + + // Массив названий месяцев + const monthNames = [ + "Январь", + "Февраль", + "Март", + "Апрель", + "Май", + "Июнь", + "Июль", + "Август", + "Сентябрь", + "Октябрь", + "Ноябрь", + "Декабрь", + ]; + + // Устанавливаем заголовок месяца и года + monthYear.textContent = `${monthNames[month]} ${year}`; + + // Получаем первый день месяца + const firstDay = new Date(year, month, 1); + // Получаем последний день месяца + const lastDay = new Date(year, month + 1, 0); + // Получаем день недели первого дня (0 - воскресенье, 1 - понедельник и т.д.) + let firstDayOfWeek = firstDay.getDay(); + // Преобразуем так, чтобы понедельник был первым днем (0) + firstDayOfWeek = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1; + + // Очищаем календарь + calendarDays.innerHTML = ""; + + // Создаём Set дат, когда были созданы заметки + const noteDates = new Set(); + allNotes.forEach((note) => { + noteDates.add(note.date); + }); + + // Получаем последний день предыдущего месяца + const prevMonthLastDay = new Date(year, month, 0).getDate(); + const prevMonth = month === 0 ? 11 : month - 1; + const prevYear = month === 0 ? year - 1 : year; + + // Добавляем дни предыдущего месяца + for (let i = firstDayOfWeek - 1; i >= 0; i--) { + const day = prevMonthLastDay - i; + const dateStr = `${String(day).padStart(2, "0")}.${String( + prevMonth + 1 + ).padStart(2, "0")}.${prevYear}`; + + const dayDiv = document.createElement("div"); + dayDiv.classList.add("calendar-day", "other-month"); + dayDiv.textContent = day; + dayDiv.dataset.date = dateStr; + + // Проверяем, есть ли заметки на этот день + if (noteDates.has(dateStr)) { + dayDiv.classList.add("has-notes"); + } + + // Проверяем, выбран ли этот день + if (selectedDateFilter === dateStr) { + dayDiv.classList.add("selected"); + } + + // Добавляем обработчик клика + dayDiv.addEventListener("click", handleDayClick); + + calendarDays.appendChild(dayDiv); + } + + // Добавляем дни текущего месяца + const today = new Date(); + for (let day = 1; day <= lastDay.getDate(); day++) { + const dateStr = `${String(day).padStart(2, "0")}.${String( + month + 1 + ).padStart(2, "0")}.${year}`; + + const dayDiv = document.createElement("div"); + dayDiv.classList.add("calendar-day"); + dayDiv.textContent = day; + dayDiv.dataset.date = dateStr; + + // Проверяем, является ли день сегодняшним + if ( + day === today.getDate() && + month === today.getMonth() && + year === today.getFullYear() + ) { + dayDiv.classList.add("today"); + } + + // Проверяем, есть ли заметки на этот день + if (noteDates.has(dateStr)) { + dayDiv.classList.add("has-notes"); + } + + // Проверяем, выбран ли этот день + if (selectedDateFilter === dateStr) { + dayDiv.classList.add("selected"); + } + + // Добавляем обработчик клика + dayDiv.addEventListener("click", handleDayClick); + + calendarDays.appendChild(dayDiv); + } + + // Добавляем дни следующего месяца + const totalCells = calendarDays.children.length; + const remainingCells = 42 - totalCells; // 6 недель по 7 дней + const nextMonth = month === 11 ? 0 : month + 1; + const nextYear = month === 11 ? year + 1 : year; + + for (let day = 1; day <= remainingCells; day++) { + const dateStr = `${String(day).padStart(2, "0")}.${String( + nextMonth + 1 + ).padStart(2, "0")}.${nextYear}`; + + const dayDiv = document.createElement("div"); + dayDiv.classList.add("calendar-day", "other-month"); + dayDiv.textContent = day; + dayDiv.dataset.date = dateStr; + + // Проверяем, есть ли заметки на этот день + if (noteDates.has(dateStr)) { + dayDiv.classList.add("has-notes"); + } + + // Проверяем, выбран ли этот день + if (selectedDateFilter === dateStr) { + dayDiv.classList.add("selected"); + } + + // Добавляем обработчик клика + dayDiv.addEventListener("click", handleDayClick); + + calendarDays.appendChild(dayDiv); + } +} + +// Обработчик клика на день в календаре +function handleDayClick(event) { + const clickedDate = event.target.dataset.date; + + // Если кликнули на тот же день, снимаем фильтр + if (selectedDateFilter === clickedDate) { + selectedDateFilter = null; + } else { + selectedDateFilter = clickedDate; + } + + // Перерисовываем заметки и календарь + renderNotes(allNotes); + renderCalendar(); + updateFilterIndicator(); +} + +// Функция для обновления индикатора фильтра +function updateFilterIndicator() { + const filterIndicator = document.getElementById("filter-indicator"); + if (!filterIndicator) return; + + if (selectedDateFilter) { + filterIndicator.style.display = "inline-block"; + filterIndicator.innerHTML = `Фильтр: ${selectedDateFilter} `; + + // Добавляем обработчик клика для кнопки сброса + const clearBtn = document.getElementById("clear-filter-btn"); + if (clearBtn) { + clearBtn.addEventListener("click", clearFilter); + } + } else { + filterIndicator.style.display = "none"; + } +} + +// Функция для сброса фильтра (глобальная) +window.clearFilter = function () { + selectedDateFilter = null; + renderNotes(allNotes); + renderCalendar(); + updateFilterIndicator(); +}; + +// Обработчики для кнопок навигации календаря +const prevMonthBtn = document.getElementById("prevMonth"); +const nextMonthBtn = document.getElementById("nextMonth"); + +if (prevMonthBtn) { + prevMonthBtn.addEventListener("click", function () { + currentDate.setMonth(currentDate.getMonth() - 1); + renderCalendar(); + }); +} + +if (nextMonthBtn) { + nextMonthBtn.addEventListener("click", function () { + currentDate.setMonth(currentDate.getMonth() + 1); + renderCalendar(); + }); +} + +// Инициализируем календарь при загрузке страницы +document.addEventListener("DOMContentLoaded", function () { + renderCalendar(); +}); diff --git a/public/notes.html b/public/notes.html index b97f309..6f08bcd 100644 --- a/public/notes.html +++ b/public/notes.html @@ -11,75 +11,100 @@ /> -
-
- 📝 Мои заметки -
-
-
- - - - - - - -
- -
- - или нажмите Alt + Enter + +
+ + или нажмите Alt + Enter +
+
-
-
- -
-
+ diff --git a/public/style.css b/public/style.css index 23a58a0..eeddfb1 100644 --- a/public/style.css +++ b/public/style.css @@ -2,10 +2,12 @@ body { font-family: "Open Sans", sans-serif; padding: 0; margin: 0; - display: flex; - flex-wrap: wrap; - justify-content: center; background: #f5f5f5; + display: flex; + justify-content: center; + align-items: flex-start; + gap: 30px; + padding: 0 15px; } header { @@ -17,7 +19,40 @@ header { .notes-header { display: flex; justify-content: space-between; - align-items: center; + align-items: flex-start; +} + +.notes-header-left { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.filter-indicator { + display: inline-block; + margin-top: 5px; + padding: 4px 10px; + background-color: #e7f3ff; + border: 1px solid #007bff; + border-radius: 15px; + font-size: 12px; + color: #007bff; + font-weight: 500; +} + +.filter-indicator button { + background: none; + border: none; + color: #dc3545; + cursor: pointer; + margin-left: 8px; + font-weight: bold; + padding: 0; + font-size: 14px; +} + +.filter-indicator button:hover { + color: #a71d2a; } .user-info { @@ -435,3 +470,134 @@ textarea:focus { color: #007bff; text-decoration: underline; } + +.center { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + width: 600px; + height: 100%; +} + +.container-leftside { + width: 200px; + height: auto; + max-width: 200px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + border-radius: 8px; + padding: 12px; + margin-top: 20px; + background: white; +} + +/* Мини-календарь */ +.mini-calendar { + width: 100%; +} + +.calendar-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.calendar-month-year { + font-size: 13px; + font-weight: bold; + color: #333; +} + +.calendar-nav { + background: none; + border: none; + font-size: 18px; + cursor: pointer; + color: #007bff; + padding: 0 3px; + transition: color 0.3s ease; +} + +.calendar-nav:hover { + color: #0056b3; +} + +.calendar-weekdays { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 1px; + margin-bottom: 3px; +} + +.calendar-weekday { + text-align: center; + font-size: 9px; + font-weight: bold; + color: #666; + padding: 3px 0; +} + +.calendar-days { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 1px; +} + +.calendar-day { + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + cursor: pointer; + border-radius: 3px; + transition: all 0.2s ease; + color: #333; + padding: 1px; + font-weight: 500; + position: relative; +} + +.calendar-day:hover { + background-color: #e7f3ff; +} + +.calendar-day.today { + background-color: #007bff; + color: white; + font-weight: bold; +} + +.calendar-day.other-month { + color: #ccc; +} + +.calendar-day.selected { + background-color: #0056b3; + color: white; + font-weight: bold; +} + +/* Индикатор для дней с заметками */ +.calendar-day.has-notes::after { + content: ""; + position: absolute; + bottom: 2px; + left: 50%; + transform: translateX(-50%); + width: 4px; + height: 4px; + background-color: #28a745; + border-radius: 50%; +} + +/* Индикатор для выбранного дня с заметками */ +.calendar-day.selected.has-notes::after { + background-color: #fff; +} + +/* Индикатор для сегодняшнего дня с заметками */ +.calendar-day.today.has-notes::after { + background-color: #fff; +}