// DOM элементы const noteInput = document.getElementById("noteInput"); const saveBtn = document.getElementById("saveBtn"); const notesList = document.getElementById("notes-container"); // Получаем кнопки markdown const boldBtn = document.getElementById("boldBtn"); const italicBtn = document.getElementById("italicBtn"); const headerBtn = document.getElementById("headerBtn"); const listBtn = document.getElementById("listBtn"); const quoteBtn = document.getElementById("quoteBtn"); const codeBtn = document.getElementById("codeBtn"); const linkBtn = document.getElementById("linkBtn"); // Глобальные переменные для заметок и фильтрации let allNotes = []; let selectedDateFilter = null; let selectedTagFilter = null; let searchQuery = ""; let searchResults = []; // Функция для получения текущей даты и времени function getFormattedDateTime() { let now = new Date(); let day = String(now.getDate()).padStart(2, "0"); let month = String(now.getMonth() + 1).padStart(2, "0"); let year = now.getFullYear(); let hours = String(now.getHours()).padStart(2, "0"); let minutes = String(now.getMinutes()).padStart(2, "0"); return { date: `${day}.${month}.${year}`, time: `${hours}:${minutes}`, }; } // Функция для авторасширения текстового поля function autoExpandTextarea(textarea) { textarea.style.height = "auto"; textarea.style.height = textarea.scrollHeight + "px"; } // Функция для извлечения тегов из текста заметки function extractTags(content) { const tagRegex = /#([а-яё\w]+)/gi; const tags = []; let match; while ((match = tagRegex.exec(content)) !== null) { const tag = match[1].toLowerCase(); if (!tags.includes(tag)) { tags.push(tag); } } return tags; } // Функция для преобразования тегов в заметках в кликабельные элементы function makeTagsClickable(content) { // Сначала находим все теги, которые еще не обернуты в HTML const tagRegex = /#([а-яё\w]+)/gi; let result = content; let match; // Создаем массив всех совпадений с их позициями const matches = []; while ((match = tagRegex.exec(content)) !== null) { matches.push({ fullMatch: match[0], tag: match[1], index: match.index, }); } // Обрабатываем совпадения в обратном порядке, чтобы не сбить индексы for (let i = matches.length - 1; i >= 0; i--) { const match = matches[i]; const beforeTag = result.substring(0, match.index); const afterTag = result.substring(match.index + match.fullMatch.length); // Проверяем, не находится ли тег уже внутри HTML-тега const lastOpenTag = beforeTag.lastIndexOf("<"); const lastCloseTag = beforeTag.lastIndexOf(">"); // Если последний открывающий тег идет после последнего закрывающего, значит мы внутри HTML-тега if (lastOpenTag > lastCloseTag) { continue; // Пропускаем этот тег } // Заменяем тег на кликабельный элемент const replacement = `${match.fullMatch}`; result = beforeTag + replacement + afterTag; } return result; } // Функция для получения всех уникальных тегов из заметок function getAllTags(notes) { const tagCounts = {}; notes.forEach((note) => { const tags = extractTags(note.content); tags.forEach((tag) => { tagCounts[tag] = (tagCounts[tag] || 0) + 1; }); }); return tagCounts; } // Функция для отображения тегов function renderTags() { const tagsContainer = document.getElementById("tagsContainer"); if (!tagsContainer) return; const tagCounts = getAllTags(allNotes); const sortedTags = Object.keys(tagCounts).sort(); if (sortedTags.length === 0) { tagsContainer.innerHTML = '
Нет тегов
'; return; } tagsContainer.innerHTML = sortedTags .map((tag) => { const count = tagCounts[tag]; const isActive = selectedTagFilter === tag ? "active" : ""; return `#${tag}${count}`; }) .join(""); // Добавляем обработчики кликов для тегов tagsContainer.querySelectorAll(".tag").forEach((tagElement) => { tagElement.addEventListener("click", handleTagClick); }); } // Обработчик клика на тег function handleTagClick(event) { const clickedTag = event.target.closest(".tag").dataset.tag; // Если кликнули на тот же тег, снимаем фильтр if (selectedTagFilter === clickedTag) { selectedTagFilter = null; } else { selectedTagFilter = clickedTag; } // Перерисовываем заметки и теги renderNotes(allNotes); renderTags(); updateFilterIndicator(); } // Привязываем авторасширение к текстовому полю для создания заметки noteInput.addEventListener("input", function () { autoExpandTextarea(noteInput); }); // Изначально запускаем для установки правильной высоты autoExpandTextarea(noteInput); // Функция для вставки markdown function insertMarkdown(tag) { 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); if (selected.startsWith(tag) && selected.endsWith(tag)) { // Если теги уже есть, удаляем их noteInput.value = `${before}${selected.slice( tag.length, -tag.length )}${after}`; noteInput.setSelectionRange(start, end - 2 * tag.length); } else if (selected.trim() === "") { // Если текст не выделен if (tag === "[Текст ссылки](URL)") { // Для ссылок создаем шаблон с двумя кавычками noteInput.value = `${before}[Текст ссылки](URL)${after}`; const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки noteInput.setSelectionRange(cursorPosition, cursorPosition + 12); } else if (tag === "- " || tag === "> " || tag === "# ") { // Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# ` noteInput.value = `${before}${tag}${after}`; const cursorPosition = start + tag.length; noteInput.setSelectionRange(cursorPosition, cursorPosition); } else { // Для остальных типов создаем два тега noteInput.value = `${before}${tag}${tag}${after}`; const cursorPosition = start + tag.length; noteInput.setSelectionRange(cursorPosition, cursorPosition); } } else { // Если текст выделен if (tag === "[Текст ссылки](URL)") { // Для ссылок используем выделенный текст вместо "Текст ссылки" noteInput.value = `${before}[${selected}](URL)${after}`; const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL noteInput.setSelectionRange(cursorPosition, cursorPosition + 3); } else if (tag === "- " || tag === "> " || tag === "# ") { // Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом noteInput.value = `${before}${tag}${selected}${after}`; const cursorPosition = start + tag.length + selected.length; noteInput.setSelectionRange(cursorPosition, cursorPosition); } else { // Для остальных типов оборачиваем выделенный текст noteInput.value = `${before}${tag}${selected}${tag}${after}`; const cursorPosition = start + tag.length + selected.length + tag.length; noteInput.setSelectionRange(cursorPosition, cursorPosition); } } noteInput.focus(); } // Функция для вставки markdown в режиме редактирования function insertMarkdownForEdit(textarea, tag) { const start = textarea.selectionStart; const end = textarea.selectionEnd; const text = textarea.value; const before = text.substring(0, start); const selected = text.substring(start, end); const after = text.substring(end); if (selected.startsWith(tag) && selected.endsWith(tag)) { // Если теги уже есть, удаляем их textarea.value = `${before}${selected.slice( tag.length, -tag.length )}${after}`; textarea.setSelectionRange(start, end - 2 * tag.length); } else if (selected.trim() === "") { // Если текст не выделен if (tag === "[Текст ссылки](URL)") { // Для ссылок создаем шаблон с двумя кавычками textarea.value = `${before}[Текст ссылки](URL)${after}`; const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки textarea.setSelectionRange(cursorPosition, cursorPosition + 12); } else if (tag === "- " || tag === "> " || tag === "# ") { // Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# ` textarea.value = `${before}${tag}${after}`; const cursorPosition = start + tag.length; textarea.setSelectionRange(cursorPosition, cursorPosition); } else { // Для остальных типов создаем два тега textarea.value = `${before}${tag}${tag}${after}`; const cursorPosition = start + tag.length; textarea.setSelectionRange(cursorPosition, cursorPosition); } } else { // Если текст выделен if (tag === "[Текст ссылки](URL)") { // Для ссылок используем выделенный текст вместо "Текст ссылки" textarea.value = `${before}[${selected}](URL)${after}`; const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL textarea.setSelectionRange(cursorPosition, cursorPosition + 3); } else if (tag === "- " || tag === "> " || tag === "# ") { // Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом textarea.value = `${before}${tag}${selected}${after}`; const cursorPosition = start + tag.length + selected.length; textarea.setSelectionRange(cursorPosition, cursorPosition); } else { // Для остальных типов оборачиваем выделенный текст textarea.value = `${before}${tag}${selected}${tag}${after}`; const cursorPosition = start + tag.length + selected.length + tag.length; textarea.setSelectionRange(cursorPosition, cursorPosition); } } textarea.focus(); } // Обработчики для кнопок markdown boldBtn.addEventListener("click", function () { insertMarkdown("**"); }); italicBtn.addEventListener("click", function () { insertMarkdown("*"); }); headerBtn.addEventListener("click", function () { insertMarkdown("# "); }); listBtn.addEventListener("click", function () { insertMarkdown("- "); }); quoteBtn.addEventListener("click", function () { insertMarkdown("> "); }); codeBtn.addEventListener("click", function () { insertMarkdown("`"); }); linkBtn.addEventListener("click", function () { insertMarkdown("[Текст ссылки](URL)"); }); // Функция для загрузки заметок с сервера async function loadNotes() { try { const response = await fetch("/api/notes"); if (!response.ok) { throw new Error("Ошибка загрузки заметок"); } const notes = await response.json(); allNotes = notes; // Сохраняем все заметки в глобальную переменную renderNotes(notes); renderCalendar(); // Обновляем календарь после загрузки заметок renderTags(); // Обновляем теги после загрузки заметок } catch (error) { console.error("Ошибка:", error); notesList.innerHTML = "

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

"; } } // Функция для поиска заметок async function searchNotes(query) { if (!query || query.trim() === "") { searchQuery = ""; searchResults = []; renderNotes(allNotes); return; } try { const params = new URLSearchParams(); params.append("q", query.trim()); // Добавляем фильтры, если они активны if (selectedTagFilter) { params.append("tag", selectedTagFilter); } if (selectedDateFilter) { params.append("date", selectedDateFilter); } const response = await fetch(`/api/notes/search?${params}`); if (!response.ok) { throw new Error("Ошибка поиска заметок"); } searchResults = await response.json(); searchQuery = query.trim(); renderNotes(searchResults); } catch (error) { console.error("Ошибка поиска:", error); searchResults = []; renderNotes(allNotes); } } // Функция для подсветки найденного текста function highlightSearchText(content, query) { if (!query || query.trim() === "") { return content; } const regex = new RegExp( `(${query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi" ); return content.replace(regex, '$1'); } // Функция для отображения заметок function renderNotes(notes) { notesList.innerHTML = ""; // Фильтруем заметки по дате и тегам let notesToDisplay = notes; if (selectedDateFilter) { notesToDisplay = notesToDisplay.filter( (note) => note.date === selectedDateFilter ); } if (selectedTagFilter) { notesToDisplay = notesToDisplay.filter((note) => { const tags = extractTags(note.content); return tags.includes(selectedTagFilter); }); } // Если нет заметок для отображения if (notesToDisplay.length === 0) { let message = "Заметок пока нет"; if (selectedDateFilter && selectedTagFilter) { message = `Нет заметок за ${selectedDateFilter} с тегом #${selectedTagFilter}`; } else if (selectedDateFilter) { message = `Нет заметок за выбранную дату (${selectedDateFilter})`; } else if (selectedTagFilter) { message = `Нет заметок с тегом #${selectedTagFilter}`; } notesList.innerHTML = `

${message}

`; return; } // Итерируемся по заметкам в обычном порядке, чтобы новые были сверху notesToDisplay.forEach(function (note) { let contentToProcess = note.content; // Сначала подсвечиваем найденный текст в исходном markdown if (searchQuery) { contentToProcess = highlightSearchText(contentToProcess, searchQuery); } // Затем преобразуем теги в кликабельные элементы const contentWithClickableTags = makeTagsClickable(contentToProcess); const parsedContent = marked.parse(contentWithClickableTags); const noteHtml = `
${note.date} ${note.time}
Редактировать
Удалить
${parsedContent}
`; notesList.insertAdjacentHTML("afterbegin", noteHtml); }); // Добавляем обработчики событий для кнопок редактирования и удаления addNoteEventListeners(); // Добавляем обработчики кликов для тегов в заметках addTagClickListeners(); // Обрабатываем длинные заметки handleLongNotes(); } // Функция для обработки длинных заметок function handleLongNotes() { const MAX_HEIGHT = 300; // Максимальная высота в пикселях document.querySelectorAll(".textNote").forEach((noteElement) => { // Проверяем высоту контента const contentHeight = noteElement.scrollHeight; if (contentHeight > MAX_HEIGHT) { // Добавляем класс для сворачивания noteElement.classList.add("collapsed"); // Создаем кнопку "Показать все" const showMoreBtn = document.createElement("button"); showMoreBtn.classList.add("show-more-btn"); showMoreBtn.textContent = "Показать полностью"; showMoreBtn.setAttribute("data-expanded", "false"); // Вставляем кнопку после заметки noteElement.parentElement.insertBefore( showMoreBtn, noteElement.nextSibling ); // Обработчик клика на кнопку showMoreBtn.addEventListener("click", function () { const isExpanded = this.getAttribute("data-expanded") === "true"; if (isExpanded) { // Сворачиваем noteElement.classList.add("collapsed"); this.textContent = "Показать полностью"; this.setAttribute("data-expanded", "false"); } else { // Разворачиваем noteElement.classList.remove("collapsed"); this.textContent = "Свернуть"; this.setAttribute("data-expanded", "true"); } }); } }); } // Функция для добавления обработчиков событий к заметкам function addNoteEventListeners() { // Обработчик удаления document.querySelectorAll("#deleteBtn").forEach((btn) => { btn.addEventListener("click", async function (event) { const noteId = event.target.dataset.id; if (confirm("Вы уверены, что хотите удалить эту заметку?")) { try { const response = await fetch(`/api/notes/${noteId}`, { method: "DELETE", }); if (!response.ok) { throw new Error("Ошибка удаления заметки"); } // Перезагружаем заметки loadNotes(); } catch (error) { console.error("Ошибка:", error); alert("Ошибка удаления заметки"); } } }); }); // Обработчик редактирования document.querySelectorAll("#editBtn").forEach((btn) => { btn.addEventListener("click", function (event) { const noteId = event.target.dataset.id; const noteContainer = event.target.closest("#note"); const noteContent = noteContainer.querySelector(".textNote"); // Создаем контейнер для markdown кнопок const markdownButtonsContainer = document.createElement("div"); markdownButtonsContainer.classList.add("markdown-buttons"); // Создаем markdown кнопки const markdownButtons = [ { id: "editBoldBtn", icon: "mdi:format-bold", tag: "**" }, { id: "editItalicBtn", icon: "mdi:format-italic", tag: "*" }, { id: "editHeaderBtn", icon: "mdi:format-header-1", tag: "# " }, { id: "editListBtn", icon: "mdi:format-list-bulleted", tag: "- " }, { id: "editQuoteBtn", icon: "mdi:format-quote-close", tag: "> " }, { id: "editCodeBtn", icon: "mdi:code-tags", tag: "`" }, { id: "editLinkBtn", icon: "mdi:link", tag: "[Текст ссылки](URL)" }, ]; markdownButtons.forEach((button) => { const btn = document.createElement("button"); btn.classList.add("btnMarkdown"); btn.id = button.id; btn.innerHTML = ``; markdownButtonsContainer.appendChild(btn); }); // Создаем textarea с уже существующим классом textInput const textarea = document.createElement("textarea"); textarea.classList.add("textInput"); // Получаем исходный markdown контент из data-атрибута или используем textContent как fallback textarea.value = noteContent.dataset.originalContent || noteContent.textContent; // Привязываем авторасширение к textarea для редактирования textarea.addEventListener("input", function () { autoExpandTextarea(textarea); }); // Контейнер для кнопки сохранения и подсказки const saveButtonContainer = document.createElement("div"); saveButtonContainer.classList.add("save-button-container"); // Кнопка сохранить const saveEditBtn = document.createElement("button"); saveEditBtn.textContent = "Сохранить"; saveEditBtn.classList.add("btnSave"); // Подсказка о горячей клавише const saveHint = document.createElement("span"); saveHint.classList.add("save-hint"); saveHint.textContent = "или нажмите Alt + Enter"; saveButtonContainer.appendChild(saveEditBtn); saveButtonContainer.appendChild(saveHint); // Функция сохранения для редактирования const saveEditNote = async function () { if (textarea.value.trim() !== "") { try { const { date, time } = getFormattedDateTime(); const response = await fetch(`/api/notes/${noteId}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ content: textarea.value, date: date, time: time, }), }); if (!response.ok) { throw new Error("Ошибка сохранения заметки"); } // Перезагружаем заметки loadNotes(); } catch (error) { console.error("Ошибка:", error); alert("Ошибка сохранения заметки"); } } }; // Обработчик горячей клавиши Alt+Enter для сохранения редактирования textarea.addEventListener("keydown", function (event) { if (event.altKey && event.key === "Enter") { event.preventDefault(); saveEditNote(); } }); // Очищаем текущий контент и вставляем markdown кнопки, textarea и контейнер с кнопкой сохранить noteContent.innerHTML = ""; noteContent.appendChild(markdownButtonsContainer); noteContent.appendChild(textarea); noteContent.appendChild(saveButtonContainer); // Применяем авторасширение после добавления в DOM setTimeout(() => { autoExpandTextarea(textarea); textarea.focus(); }, 0); // Добавляем обработчики для markdown кнопок редактирования markdownButtons.forEach((button) => { const btn = document.getElementById(button.id); btn.addEventListener("click", function () { insertMarkdownForEdit(textarea, button.tag); }); }); // Обработчик сохранения редактирования saveEditBtn.addEventListener("click", saveEditNote); }); }); } // Функция для добавления обработчиков кликов на теги в заметках function addTagClickListeners() { document.querySelectorAll(".textNote .tag-in-note").forEach((tagElement) => { tagElement.addEventListener("click", function (event) { event.preventDefault(); event.stopPropagation(); const clickedTag = this.dataset.tag.toLowerCase(); // Если кликнули на тот же тег, снимаем фильтр if (selectedTagFilter === clickedTag) { selectedTagFilter = null; } else { selectedTagFilter = clickedTag; } // Перерисовываем заметки и теги renderNotes(allNotes); renderTags(); updateFilterIndicator(); }); }); } // Функция сохранения заметки (вынесена отдельно для повторного использования) async function saveNote() { if (noteInput.value.trim() !== "") { try { const { date, time } = getFormattedDateTime(); const response = await fetch("/api/notes", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ content: noteInput.value, date: date, time: time, }), }); if (!response.ok) { throw new Error("Ошибка сохранения заметки"); } // Очищаем поле ввода и перезагружаем заметки noteInput.value = ""; noteInput.style.height = "auto"; loadNotes(); } catch (error) { console.error("Ошибка:", error); alert("Ошибка сохранения заметки"); } } } // Обработчик сохранения новой заметки saveBtn.addEventListener("click", saveNote); // Обработчик горячей клавиши Alt+Enter для сохранения заметки noteInput.addEventListener("keydown", function (event) { if (event.altKey && event.key === "Enter") { event.preventDefault(); // Предотвращаем стандартное поведение saveNote(); } }); // Загружаем заметки при загрузке страницы document.addEventListener("DOMContentLoaded", function () { loadUserInfo(); loadNotes(); updateFilterIndicator(); }); // Функция для загрузки информации о пользователе async function loadUserInfo() { try { 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" ); if (usernameDisplay) { usernameDisplay.innerHTML = ` ${user.username}`; // Делаем ник кликабельным для перехода в личный кабинет usernameDisplay.style.cursor = "pointer"; usernameDisplay.addEventListener("click", function () { window.location.href = "/profile"; }); } // Аватарка скрыта на странице заметок if (userAvatarContainer) { userAvatarContainer.style.display = "none"; } } } catch (error) { 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(); renderTags(); updateFilterIndicator(); } // Функция для обновления индикатора фильтра function updateFilterIndicator() { const filterIndicator = document.getElementById("filter-indicator"); if (!filterIndicator) return; const filters = []; if (searchQuery) { filters.push(`Поиск: "${searchQuery}"`); } if (selectedDateFilter) { filters.push(`Дата: ${selectedDateFilter}`); } if (selectedTagFilter) { filters.push(`Тег: #${selectedTagFilter}`); } if (filters.length > 0) { filterIndicator.style.display = "inline-block"; filterIndicator.innerHTML = `Фильтр: ${filters.join( ", " )} `; // Добавляем обработчик клика для кнопки сброса const clearBtn = document.getElementById("clear-filter-btn"); if (clearBtn) { clearBtn.addEventListener("click", clearFilter); } } else { filterIndicator.style.display = "none"; } } // Функция для сброса фильтра (глобальная) window.clearFilter = function () { selectedDateFilter = null; selectedTagFilter = null; searchQuery = ""; searchResults = []; // Очищаем поле поиска const searchInput = document.getElementById("searchInput"); if (searchInput) { searchInput.value = ""; } // Скрываем кнопку очистки поиска const clearSearchBtn = document.getElementById("clearSearchBtn"); if (clearSearchBtn) { clearSearchBtn.style.display = "none"; } renderNotes(allNotes); renderCalendar(); renderTags(); 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(); // Инициализируем поиск initSearch(); }); // Функция для инициализации поиска function initSearch() { const searchInput = document.getElementById("searchInput"); const clearSearchBtn = document.getElementById("clearSearchBtn"); if (!searchInput || !clearSearchBtn) return; // Обработчик ввода в поле поиска с задержкой let searchTimeout; searchInput.addEventListener("input", function () { clearTimeout(searchTimeout); const query = this.value; // Показываем/скрываем кнопку очистки if (query.trim()) { clearSearchBtn.style.display = "block"; } else { clearSearchBtn.style.display = "none"; } // Задержка перед поиском для оптимизации searchTimeout = setTimeout(() => { searchNotes(query); updateFilterIndicator(); }, 300); }); // Обработчик клика на кнопку очистки поиска clearSearchBtn.addEventListener("click", function () { searchInput.value = ""; this.style.display = "none"; searchQuery = ""; searchResults = []; renderNotes(allNotes); updateFilterIndicator(); }); // Обработчик клавиши Escape для очистки поиска searchInput.addEventListener("keydown", function (event) { if (event.key === "Escape") { this.value = ""; clearSearchBtn.style.display = "none"; searchQuery = ""; searchResults = []; renderNotes(allNotes); updateFilterIndicator(); } }); }