// 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; // Функция для получения текущей даты и времени 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"; } // Привязываем авторасширение к текстовому полю для создания заметки 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(); // Обновляем календарь после загрузки заметок } catch (error) { console.error("Ошибка:", error); notesList.innerHTML = "

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

"; } } // Функция для отображения заметок 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; } // Итерируемся по заметкам в обычном порядке, чтобы новые были сверху notesToDisplay.forEach(function (note) { const noteHtml = `
${note.date} ${note.time}
Редактировать
Удалить
${marked.parse(note.content)}
`; notesList.insertAdjacentHTML("afterbegin", noteHtml); }); // Добавляем обработчики событий для кнопок редактирования и удаления addNoteEventListeners(); } // Функция для добавления обработчиков событий к заметкам 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 = ``; 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("Ошибка сохранения заметки"); } } }); }); }); } // Функция сохранения заметки (вынесена отдельно для повторного использования) 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(); 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(); });