// 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");
const imageBtn = document.getElementById("imageBtn");
// Элементы для загрузки изображений
const imageInput = document.getElementById("imageInput");
const imagePreviewContainer = document.getElementById("imagePreviewContainer");
const imagePreviewList = document.getElementById("imagePreviewList");
const clearImagesBtn = document.getElementById("clearImagesBtn");
// Модальное окно для просмотра изображений
const imageModal = document.getElementById("imageModal");
const modalImage = document.getElementById("modalImage");
const modalClose = document.querySelector(".image-modal-close");
// Массив для хранения выбранных изображений
let selectedImages = [];
// Глобальные переменные для заметок и фильтрации
let allNotes = [];
let selectedDateFilter = null;
let selectedTagFilter = null;
let searchQuery = "";
let searchResults = [];
let notesCache = null; // Кэш для заметок
let lastLoadTime = 0; // Время последней загрузки
// Функция для получения текущей даты и времени
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", async (event) => await handleTagClick(event));
});
}
// Обработчик клика на тег
async function handleTagClick(event) {
const clickedTag = event.target.closest(".tag").dataset.tag;
// Если кликнули на тот же тег, снимаем фильтр
if (selectedTagFilter === clickedTag) {
selectedTagFilter = null;
} else {
selectedTagFilter = clickedTag;
}
// Перерисовываем заметки и теги
await 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)");
});
// Обработчик для кнопки загрузки изображений
imageBtn.addEventListener("click", function () {
imageInput.click();
});
// Обработчик выбора файлов
imageInput.addEventListener("change", function (event) {
const files = Array.from(event.target.files);
files.forEach(file => {
if (file.type.startsWith('image/')) {
selectedImages.push(file);
}
});
updateImagePreview();
});
// Обработчик очистки всех изображений
clearImagesBtn.addEventListener("click", function () {
selectedImages = [];
updateImagePreview();
imageInput.value = "";
});
// Обработчики модального окна
modalClose.addEventListener("click", function () {
imageModal.style.display = "none";
});
imageModal.addEventListener("click", function (event) {
if (event.target === imageModal) {
imageModal.style.display = "none";
}
});
// Закрытие модального окна по Escape
document.addEventListener("keydown", function (event) {
if (event.key === "Escape" && imageModal.style.display === "block") {
imageModal.style.display = "none";
}
});
// Функция для обновления превью изображений
function updateImagePreview() {
if (selectedImages.length === 0) {
imagePreviewContainer.style.display = "none";
return;
}
imagePreviewContainer.style.display = "block";
imagePreviewList.innerHTML = "";
selectedImages.forEach((file, index) => {
const reader = new FileReader();
reader.onload = function (e) {
const previewItem = document.createElement("div");
previewItem.className = "image-preview-item";
previewItem.innerHTML = `
${file.name}
`;
imagePreviewList.appendChild(previewItem);
// Обработчик удаления изображения
const removeBtn = previewItem.querySelector(".remove-image-btn");
removeBtn.addEventListener("click", function () {
selectedImages.splice(index, 1);
updateImagePreview();
});
};
reader.readAsDataURL(file);
});
}
// Функция для отображения изображения в модальном окне
function showImageModal(imageSrc) {
console.log('showImageModal called with:', imageSrc);
try {
modalImage.src = imageSrc;
imageModal.style.display = "block";
console.log('Modal opened successfully');
} catch (error) {
console.error('Error in showImageModal:', error);
}
}
// Функция для загрузки изображений на сервер
async function uploadImages(noteId) {
if (selectedImages.length === 0) {
return [];
}
const formData = new FormData();
selectedImages.forEach(file => {
formData.append("images", file);
});
try {
const response = await fetch(`/api/notes/${noteId}/images`, {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error("Ошибка загрузки изображений");
}
const result = await response.json();
return result.images || [];
} catch (error) {
console.error("Ошибка загрузки изображений:", error);
return [];
}
}
// Функция для получения изображений заметки
async function getNoteImages(noteId) {
try {
const response = await fetch(`/api/notes/${noteId}/images`);
if (!response.ok) {
throw new Error("Ошибка получения изображений");
}
return await response.json();
} catch (error) {
console.error("Ошибка получения изображений:", error);
return [];
}
}
// Функция для удаления изображения заметки
async function deleteNoteImage(noteId, imageId) {
try {
const response = await fetch(`/api/notes/${noteId}/images/${imageId}`, {
method: "DELETE",
});
if (!response.ok) {
throw new Error("Ошибка удаления изображения");
}
return true;
} catch (error) {
console.error("Ошибка удаления изображения:", error);
return false;
}
}
// Функция для загрузки заметок с сервера
async function loadNotes(forceReload = false) {
const now = Date.now();
const CACHE_DURATION = 30000; // 30 секунд кэширования
// Используем кэш, если он не устарел и не требуется принудительная перезагрузка
if (!forceReload && notesCache && (now - lastLoadTime) < CACHE_DURATION) {
allNotes = notesCache;
await renderNotes(notesCache);
renderCalendar();
renderTags();
renderCalendarMobile();
renderTagsMobile();
return;
}
// Показываем индикатор загрузки
showLoadingIndicator();
try {
const response = await fetch("/api/notes");
if (!response.ok) {
throw new Error("Ошибка загрузки заметок");
}
const notes = await response.json();
allNotes = notes; // Сохраняем все заметки в глобальную переменную
notesCache = notes; // Сохраняем в кэш
lastLoadTime = now;
await renderNotes(notes);
renderCalendar(); // Обновляем календарь после загрузки заметок
renderTags(); // Обновляем теги после загрузки заметок
renderCalendarMobile(); // Обновляем мобильный календарь после загрузки заметок
renderTagsMobile(); // Обновляем мобильные теги после загрузки заметок
} catch (error) {
console.error("Ошибка:", error);
notesList.innerHTML = "
Ошибка загрузки заметок
";
} finally {
// Скрываем индикатор загрузки
hideLoadingIndicator();
}
}
// Функция для показа индикатора загрузки
function showLoadingIndicator() {
if (!document.getElementById("loading-indicator")) {
const loadingDiv = document.createElement("div");
loadingDiv.id = "loading-indicator";
loadingDiv.innerHTML = `
Загрузка заметок...
`;
document.body.appendChild(loadingDiv);
}
}
// Функция для скрытия индикатора загрузки
function hideLoadingIndicator() {
const loadingIndicator = document.getElementById("loading-indicator");
if (loadingIndicator) {
loadingIndicator.remove();
}
}
// Функция для поиска заметок
async function searchNotes(query) {
if (!query || query.trim() === "") {
searchQuery = "";
searchResults = [];
await 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();
await renderNotes(searchResults);
} catch (error) {
console.error("Ошибка поиска:", error);
searchResults = [];
await 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');
}
// Функция для отображения заметок
async 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;
}
// Итерируемся по заметкам в обычном порядке, чтобы новые были сверху
for (const note of notesToDisplay) {
let contentToProcess = note.content;
// Сначала подсвечиваем найденный текст в исходном markdown
if (searchQuery) {
contentToProcess = highlightSearchText(contentToProcess, searchQuery);
}
// Затем преобразуем теги в кликабельные элементы
const contentWithClickableTags = makeTagsClickable(contentToProcess);
const parsedContent = marked.parse(contentWithClickableTags);
// Используем изображения, которые уже пришли с заметкой
const noteImages = Array.isArray(note.images) ? note.images : [];
let imagesHtml = "";
if (noteImages.length > 0) {
imagesHtml = '
';
noteImages.forEach(image => {
imagesHtml += `
`;
});
imagesHtml += '
';
}
const noteHtml = `
${note.date} ${note.time}
Редактировать
Удалить
${parsedContent}
${imagesHtml}
`;
notesList.insertAdjacentHTML("afterbegin", noteHtml);
}
// Добавляем обработчики событий для кнопок редактирования и удаления
addNoteEventListeners();
// Добавляем обработчики кликов для тегов в заметках
addTagClickListeners();
// Добавляем обработчики для изображений в заметках
addImageEventListeners();
// Обрабатываем длинные заметки
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("Ошибка удаления заметки");
}
// Перезагружаем заметки
await loadNotes(true);
} 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");
// Разворачиваем заметку при редактировании
noteContent.classList.remove("collapsed");
// Скрываем кнопку "Показать полностью" если она есть
const showMoreBtn = noteContainer.querySelector(".show-more-btn");
if (showMoreBtn) {
showMoreBtn.style.display = "none";
}
// Создаем контейнер для 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)" },
{ id: "editImageBtn", icon: "mdi:image-plus", tag: "image" },
];
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 editImageInput = document.createElement("input");
editImageInput.type = "file";
editImageInput.accept = "image/*";
editImageInput.multiple = true;
editImageInput.style.display = "none";
editImageInput.id = `editImageInput-${noteId}`;
const editImagePreviewContainer = document.createElement("div");
editImagePreviewContainer.id = `editImagePreviewContainer-${noteId}`;
editImagePreviewContainer.classList.add("image-preview-container");
editImagePreviewContainer.style.display = "none";
const editImagePreviewHeader = document.createElement("div");
editImagePreviewHeader.classList.add("image-preview-header");
editImagePreviewHeader.innerHTML = `
Новые изображения:
`;
const editImagePreviewList = document.createElement("div");
editImagePreviewList.id = `editImagePreviewList-${noteId}`;
editImagePreviewList.classList.add("image-preview-list");
editImagePreviewContainer.appendChild(editImagePreviewHeader);
editImagePreviewContainer.appendChild(editImagePreviewList);
// Массив для хранения новых изображений в режиме редактирования
const editSelectedImages = [];
// Контейнер для кнопки сохранения и подсказки
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 updateEditImagePreview = function() {
if (editSelectedImages.length === 0) {
editImagePreviewContainer.style.display = "none";
return;
}
editImagePreviewContainer.style.display = "block";
editImagePreviewList.innerHTML = "";
editSelectedImages.forEach((file, index) => {
const reader = new FileReader();
reader.onload = function (e) {
const previewItem = document.createElement("div");
previewItem.className = "image-preview-item";
previewItem.innerHTML = `
${file.name}
`;
editImagePreviewList.appendChild(previewItem);
// Обработчик удаления изображения
const removeBtn = previewItem.querySelector(".remove-image-btn");
removeBtn.addEventListener("click", function () {
editSelectedImages.splice(index, 1);
updateEditImagePreview();
});
};
reader.readAsDataURL(file);
});
};
// Функция загрузки изображений для режима редактирования
const uploadEditImages = async function(noteId) {
if (editSelectedImages.length === 0) {
return [];
}
const formData = new FormData();
editSelectedImages.forEach(file => {
formData.append("images", file);
});
try {
const response = await fetch(`/api/notes/${noteId}/images`, {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error("Ошибка загрузки изображений");
}
const result = await response.json();
return result.images || [];
} catch (error) {
console.error("Ошибка загрузки изображений:", error);
return [];
}
};
// Функция сохранения для редактирования
const saveEditNote = async function () {
if (textarea.value.trim() !== "" || editSelectedImages.length > 0) {
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("Ошибка сохранения заметки");
}
// Загружаем новые изображения, если они есть
if (editSelectedImages.length > 0) {
await uploadEditImages(noteId);
}
// Перезагружаем заметки
await loadNotes(true);
} catch (error) {
console.error("Ошибка:", error);
alert("Ошибка сохранения заметки");
}
}
};
// Обработчик горячей клавиши Alt+Enter для сохранения редактирования
textarea.addEventListener("keydown", function (event) {
if (event.altKey && event.key === "Enter") {
event.preventDefault();
saveEditNote();
}
});
// Обработчики для загрузки изображений в режиме редактирования
editImageInput.addEventListener("change", function (event) {
const files = Array.from(event.target.files);
files.forEach(file => {
if (file.type.startsWith('image/')) {
editSelectedImages.push(file);
}
});
updateEditImagePreview();
});
// Обработчик очистки всех изображений в режиме редактирования
const editClearImagesBtn = editImagePreviewHeader.querySelector(`#editClearImagesBtn-${noteId}`);
editClearImagesBtn.addEventListener("click", function () {
editSelectedImages.length = 0;
updateEditImagePreview();
editImageInput.value = "";
});
// Очищаем текущий контент и вставляем markdown кнопки, textarea, элементы для изображений и контейнер с кнопкой сохранить
noteContent.innerHTML = "";
noteContent.appendChild(markdownButtonsContainer);
noteContent.appendChild(textarea);
noteContent.appendChild(editImageInput);
noteContent.appendChild(editImagePreviewContainer);
noteContent.appendChild(saveButtonContainer);
// Применяем авторасширение после добавления в DOM
setTimeout(() => {
autoExpandTextarea(textarea);
textarea.focus();
}, 0);
// Добавляем обработчики для markdown кнопок редактирования
markdownButtons.forEach((button) => {
const btn = document.getElementById(button.id);
btn.addEventListener("click", function () {
if (button.tag === "image") {
// Для кнопки изображения открываем диалог выбора файлов
editImageInput.click();
} else {
insertMarkdownForEdit(textarea, button.tag);
}
});
});
// Обработчик сохранения редактирования
saveEditBtn.addEventListener("click", saveEditNote);
});
});
}
// Функция для добавления обработчиков кликов на теги в заметках
function addTagClickListeners() {
document.querySelectorAll(".textNote .tag-in-note").forEach((tagElement) => {
tagElement.addEventListener("click", async function (event) {
event.preventDefault();
event.stopPropagation();
const clickedTag = this.dataset.tag.toLowerCase();
// Если кликнули на тот же тег, снимаем фильтр
if (selectedTagFilter === clickedTag) {
selectedTagFilter = null;
} else {
selectedTagFilter = clickedTag;
}
// Перерисовываем заметки и теги
await renderNotes(allNotes);
renderTags();
updateFilterIndicator();
});
});
}
// Функция для добавления обработчиков для изображений в заметках
function addImageEventListeners() {
// Обработчики для кликов на изображения (открытие в модальном окне)
document.querySelectorAll(".note-image").forEach((imageElement) => {
// Проверяем, не добавлен ли уже обработчик
if (imageElement._clickHandler) {
return; // Пропускаем, если обработчик уже добавлен
}
// Создаем новый обработчик
imageElement._clickHandler = function (event) {
event.preventDefault();
event.stopPropagation();
const imageSrc = this.dataset.imageSrc;
console.log('Image clicked, src:', imageSrc); // Для отладки
if (imageSrc) {
showImageModal(imageSrc);
}
};
imageElement.addEventListener("click", imageElement._clickHandler);
});
// Обработчики для кнопок удаления изображений
document.querySelectorAll(".remove-note-image-btn").forEach((buttonElement) => {
// Удаляем старые обработчики, если они есть
if (buttonElement._clickHandler) {
buttonElement.removeEventListener("click", buttonElement._clickHandler);
}
// Создаем новый обработчик
buttonElement._clickHandler = async function (event) {
event.preventDefault();
event.stopPropagation();
const noteId = this.dataset.noteId;
const imageId = this.dataset.imageId;
if (noteId && imageId && confirm("Вы уверены, что хотите удалить это изображение?")) {
const success = await deleteNoteImage(noteId, imageId);
if (success) {
await loadNotes(true); // Перезагружаем заметки
} else {
alert("Ошибка удаления изображения");
}
}
};
buttonElement.addEventListener("click", buttonElement._clickHandler);
});
}
// Функция сохранения заметки (вынесена отдельно для повторного использования)
async function saveNote() {
if (noteInput.value.trim() !== "" || selectedImages.length > 0) {
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("Ошибка сохранения заметки");
}
const noteData = await response.json();
const noteId = noteData.id;
// Загружаем изображения, если они есть
if (selectedImages.length > 0) {
await uploadImages(noteId);
}
// Очищаем поле ввода и изображения, перезагружаем заметки
noteInput.value = "";
noteInput.style.height = "auto";
selectedImages = [];
updateImagePreview();
imageInput.value = "";
await loadNotes(true);
} 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 () {
// Проверяем аутентификацию при загрузке страницы
checkAuthentication();
loadUserInfo();
loadNotes();
updateFilterIndicator();
// Добавляем обработчик для кнопки выхода
setupLogoutHandler();
});
// Функция для настройки обработчика выхода
function setupLogoutHandler() {
const logoutForms = document.querySelectorAll('form[action="/logout"]');
logoutForms.forEach(form => {
form.addEventListener('submit', function(e) {
// Очищаем localStorage перед выходом
localStorage.removeItem('isAuthenticated');
localStorage.removeItem('username');
});
});
}
// Функция для проверки аутентификации
async function checkAuthentication() {
const isAuthenticated = localStorage.getItem('isAuthenticated');
if (isAuthenticated !== 'true') {
// Если пользователь не аутентифицирован, перенаправляем на страницу входа
window.location.href = "/";
return;
}
// Проверяем, что сессия на сервере еще действительна
try {
const response = await fetch("/api/auth/status");
if (!response.ok) {
// Если сессия недействительна, очищаем localStorage и перенаправляем
localStorage.removeItem('isAuthenticated');
localStorage.removeItem('username');
window.location.href = "/";
return;
}
const authData = await response.json();
if (!authData.authenticated) {
// Если сервер говорит, что пользователь не аутентифицирован
localStorage.removeItem('isAuthenticated');
localStorage.removeItem('username');
window.location.href = "/";
return;
}
} catch (error) {
console.error("Ошибка проверки аутентификации:", error);
// В случае ошибки сети, оставляем пользователя на странице
// но показываем предупреждение
console.warn("Не удалось проверить статус аутентификации");
}
}
// Функция для загрузки информации о пользователе
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", async (event) => await handleDayClick(event));
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", async (event) => await handleDayClick(event));
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", async (event) => await handleDayClick(event));
calendarDays.appendChild(dayDiv);
}
}
// Обработчик клика на день в календаре
async function handleDayClick(event) {
const clickedDate = event.target.dataset.date;
// Если кликнули на тот же день, снимаем фильтр
if (selectedDateFilter === clickedDate) {
selectedDateFilter = null;
} else {
selectedDateFilter = clickedDate;
}
// Перерисовываем заметки, календарь и теги
await 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 = async 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";
}
await renderNotes(allNotes);
renderCalendar();
renderTags();
updateFilterIndicator();
};
// Глобальные функции для работы с изображениями (оставляем только showImageModal для совместимости)
// window.showImageModal удалена, так как она создавала рекурсию
// Обработчики для кнопок навигации календаря
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", async function () {
searchInput.value = "";
this.style.display = "none";
searchQuery = "";
searchResults = [];
await renderNotes(allNotes);
updateFilterIndicator();
});
// Обработчик клавиши Escape для очистки поиска
searchInput.addEventListener("keydown", async function (event) {
if (event.key === "Escape") {
this.value = "";
clearSearchBtn.style.display = "none";
searchQuery = "";
searchResults = [];
await renderNotes(allNotes);
updateFilterIndicator();
}
});
}
// ==================== МОБИЛЬНЫЙ СЛАЙДЕР ====================
// Функция для отображения календаря в мобильном слайдере
function renderCalendarMobile() {
const calendarDays = document.getElementById("calendarDaysMobile");
const monthYear = document.getElementById("monthYearMobile");
// Проверяем, существуют ли элементы календаря для мобильной версии
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", async (event) => await handleDayClickMobile(event));
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", async (event) => await handleDayClickMobile(event));
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", async (event) => await handleDayClickMobile(event));
calendarDays.appendChild(dayDiv);
}
}
// Обработчик клика на день в календаре для мобильной версии
async function handleDayClickMobile(event) {
const clickedDate = event.target.dataset.date;
// Если кликнули на тот же день, снимаем фильтр
if (selectedDateFilter === clickedDate) {
selectedDateFilter = null;
} else {
selectedDateFilter = clickedDate;
}
// Перерисовываем заметки, оба календаря и теги
await renderNotes(allNotes);
renderCalendar();
renderCalendarMobile();
renderTags();
renderTagsMobile();
updateFilterIndicator();
}
// Функция для отображения тегов в мобильном слайдере
function renderTagsMobile() {
const tagsContainer = document.getElementById("tagsContainerMobile");
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", async (event) => await handleTagClickMobile(event));
});
}
// Обработчик клика на тег в мобильном слайдере
async function handleTagClickMobile(event) {
const clickedTag = event.target.closest(".tag").dataset.tag;
// Если кликнули на тот же тег, снимаем фильтр
if (selectedTagFilter === clickedTag) {
selectedTagFilter = null;
} else {
selectedTagFilter = clickedTag;
}
// Перерисовываем заметки, теги и оба календаря
await renderNotes(allNotes);
renderTags();
renderTagsMobile();
renderCalendar();
renderCalendarMobile();
updateFilterIndicator();
}
// Инициализация мобильного слайдера
document.addEventListener("DOMContentLoaded", function () {
const mobileMenuBtn = document.getElementById("mobileMenuBtn");
const sidebarCloseBtn = document.getElementById("sidebarCloseBtn");
const mobileSidebar = document.getElementById("mobileSidebar");
const sidebarOverlay = document.getElementById("sidebarOverlay");
// Открытие слайдера при клике на кнопку меню
if (mobileMenuBtn) {
mobileMenuBtn.addEventListener("click", function () {
mobileSidebar.classList.add("open");
sidebarOverlay.classList.add("open");
document.body.style.overflow = "hidden";
});
}
// Закрытие слайдера при клике на кнопку закрытия
if (sidebarCloseBtn) {
sidebarCloseBtn.addEventListener("click", function () {
mobileSidebar.classList.remove("open");
sidebarOverlay.classList.remove("open");
document.body.style.overflow = "auto";
});
}
// Закрытие слайдера при клике на оверлей
if (sidebarOverlay) {
sidebarOverlay.addEventListener("click", function () {
mobileSidebar.classList.remove("open");
sidebarOverlay.classList.remove("open");
document.body.style.overflow = "auto";
});
}
// Инициализация мобильного поиска
initSearchMobile();
// Инициализация мобильного календаря
renderCalendarMobile();
renderTagsMobile();
// Обработчики для кнопок навигации мобильного календаря
const prevMonthBtnMobile = document.getElementById("prevMonthMobile");
const nextMonthBtnMobile = document.getElementById("nextMonthMobile");
if (prevMonthBtnMobile) {
prevMonthBtnMobile.addEventListener("click", function () {
currentDate.setMonth(currentDate.getMonth() - 1);
renderCalendar();
renderCalendarMobile();
});
}
if (nextMonthBtnMobile) {
nextMonthBtnMobile.addEventListener("click", function () {
currentDate.setMonth(currentDate.getMonth() + 1);
renderCalendar();
renderCalendarMobile();
});
}
});
// Функция для инициализации мобильного поиска
function initSearchMobile() {
const searchInput = document.getElementById("searchInputMobile");
const clearSearchBtn = document.getElementById("clearSearchBtnMobile");
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);
// Обновляем основное поле поиска
const mainSearchInput = document.getElementById("searchInput");
if (mainSearchInput) {
mainSearchInput.value = query;
}
const mainClearSearchBtn = document.getElementById("clearSearchBtn");
if (mainClearSearchBtn) {
if (query.trim()) {
mainClearSearchBtn.style.display = "block";
} else {
mainClearSearchBtn.style.display = "none";
}
}
updateFilterIndicator();
}, 300);
});
// Обработчик клика на кнопку очистки поиска
clearSearchBtn.addEventListener("click", async function () {
searchInput.value = "";
this.style.display = "none";
searchQuery = "";
searchResults = [];
// Обновляем основное поле поиска
const mainSearchInput = document.getElementById("searchInput");
if (mainSearchInput) {
mainSearchInput.value = "";
}
const mainClearSearchBtn = document.getElementById("clearSearchBtn");
if (mainClearSearchBtn) {
mainClearSearchBtn.style.display = "none";
}
await renderNotes(allNotes);
updateFilterIndicator();
});
// Обработчик клавиши Escape для очистки поиска
searchInput.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
this.value = "";
clearSearchBtn.style.display = "none";
searchQuery = "";
searchResults = [];
// Обновляем основное поле поиска
const mainSearchInput = document.getElementById("searchInput");
if (mainSearchInput) {
mainSearchInput.value = "";
}
const mainClearSearchBtn = document.getElementById("clearSearchBtn");
if (mainClearSearchBtn) {
mainClearSearchBtn.style.display = "none";
}
renderNotes(allNotes);
updateFilterIndicator();
}
});
}