NoteJS/public/app.js
Fovway 034208fc56 feat: Добавлена функциональность тегов с фильтрацией
- Добавлена секция тегов под календарем с отображением всех уникальных тегов
- Реализована фильтрация заметок по тегам при клике на тег
- Добавлены кликабельные теги в самих заметках для интуитивной навигации
- Теги автоматически извлекаются из текста заметок в формате #название
- Добавлены счетчики для каждого тега, показывающие количество заметок
- Реализован индикатор активного фильтра с возможностью сброса
- Поддержка комбинированной фильтрации по дате и тегам
- Стилизованные теги с hover-эффектами и анимациями
- Обновление тегов в реальном времени при создании/редактировании заметок

Файлы изменены:
- public/notes.html: добавлена HTML-структура для секции тегов
- public/style.css: стили для тегов в боковой панели и в заметках
- public/app.js: логика извлечения тегов, фильтрации и обработчики кликов
2025-10-18 20:16:59 +07:00

835 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
// Функция для получения текущей даты и времени
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+)/g;
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) {
const tagRegex = /#(\w+)/g;
return content.replace(
tagRegex,
'<span class="tag-in-note" data-tag="$1">#$1</span>'
);
}
// Функция для получения всех уникальных тегов из заметок
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 =
'<div style="font-size: 10px; color: #999; text-align: center;">Нет тегов</div>';
return;
}
tagsContainer.innerHTML = sortedTags
.map((tag) => {
const count = tagCounts[tag];
const isActive = selectedTagFilter === tag ? "active" : "";
return `<span class="tag ${isActive}" data-tag="${tag}">#${tag}<span class="tag-count">${count}</span></span>`;
})
.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 = "<p>Ошибка загрузки заметок</p>";
}
}
// Функция для отображения заметок
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 = `<div class="container"><p style="text-align: center; color: #999;">${message}</p></div>`;
return;
}
// Итерируемся по заметкам в обычном порядке, чтобы новые были сверху
notesToDisplay.forEach(function (note) {
// Преобразуем теги в кликабельные элементы перед парсингом markdown
const contentWithClickableTags = makeTagsClickable(note.content);
const parsedContent = marked.parse(contentWithClickableTags);
const noteHtml = `
<div id="note" class="container">
<div class="date">
${note.date} ${note.time}
<div id="editBtn" class="notesHeaderBtn" data-id="${
note.id
}">Редактировать</div>
<div id="deleteBtn" class="notesHeaderBtn" data-id="${
note.id
}">Удалить</div>
</div>
<div class="textNote" data-original-content="${note.content.replace(
/"/g,
"&quot;"
)}">${parsedContent}</div>
</div>
`;
notesList.insertAdjacentHTML("afterbegin", noteHtml);
});
// Добавляем обработчики событий для кнопок редактирования и удаления
addNoteEventListeners();
// Добавляем обработчики кликов для тегов в заметках
addTagClickListeners();
}
// Функция для добавления обработчиков событий к заметкам
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: "fas fa-bold", tag: "**" },
{ id: "editItalicBtn", icon: "fas fa-italic", tag: "*" },
{ id: "editHeaderBtn", icon: "fas fa-heading", tag: "# " },
{ id: "editListBtn", icon: "fas fa-list-ul", tag: "- " },
{ id: "editQuoteBtn", icon: "fas fa-quote-right", tag: "> " },
{ id: "editCodeBtn", icon: "fas fa-code", tag: "`" },
{ id: "editLinkBtn", icon: "fas fa-link", tag: "[Текст ссылки](URL)" },
];
markdownButtons.forEach((button) => {
const btn = document.createElement("button");
btn.classList.add("btnMarkdown");
btn.id = button.id;
btn.innerHTML = `<i class="${button.icon}"></i>`;
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);
});
autoExpandTextarea(textarea);
// Кнопка сохранить
const saveEditBtn = document.createElement("button");
saveEditBtn.textContent = "Сохранить";
saveEditBtn.classList.add("btnSave");
// Очищаем текущий контент и вставляем markdown кнопки, textarea и кнопку сохранить
noteContent.innerHTML = "";
noteContent.appendChild(markdownButtonsContainer);
noteContent.appendChild(textarea);
noteContent.appendChild(saveEditBtn);
// Добавляем обработчики для markdown кнопок редактирования
markdownButtons.forEach((button) => {
const btn = document.getElementById(button.id);
btn.addEventListener("click", function () {
insertMarkdownForEdit(textarea, button.tag);
});
});
// Обработчик сохранения редактирования
saveEditBtn.addEventListener("click", 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("Ошибка сохранения заметки");
}
}
});
});
});
}
// Функция для добавления обработчиков кликов на теги в заметках
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.textContent = `👤 ${user.username}`;
// Делаем ник кликабельным для перехода в личный кабинет
usernameDisplay.style.cursor = "pointer";
usernameDisplay.addEventListener("click", function () {
window.location.href = "/profile";
});
}
// Отображаем аватарку, если она есть
if (user.avatar && userAvatar && userAvatarContainer) {
userAvatar.src = user.avatar;
userAvatarContainer.style.display = "inline-block";
// Аватарка также кликабельна
userAvatarContainer.style.cursor = "pointer";
userAvatarContainer.addEventListener("click", function () {
window.location.href = "/profile";
});
}
}
} 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 (selectedDateFilter) {
filters.push(`Дата: ${selectedDateFilter}`);
}
if (selectedTagFilter) {
filters.push(`Тег: #${selectedTagFilter}`);
}
if (filters.length > 0) {
filterIndicator.style.display = "inline-block";
filterIndicator.innerHTML = `Фильтр: ${filters.join(
", "
)} <button id="clear-filter-btn">✕</button>`;
// Добавляем обработчик клика для кнопки сброса
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;
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();
});