diff --git a/clickable_tags_final_screenshot.png b/clickable_tags_final_screenshot.png new file mode 100644 index 0000000..9e65dcf Binary files /dev/null and b/clickable_tags_final_screenshot.png differ diff --git a/public/app.js b/public/app.js index 826a1e2..2cd5180 100644 --- a/public/app.js +++ b/public/app.js @@ -15,6 +15,7 @@ const linkBtn = document.getElementById("linkBtn"); // Глобальные переменные для заметок и фильтрации let allNotes = []; let selectedDateFilter = null; +let selectedTagFilter = null; // Функция для получения текущей даты и времени function getFormattedDateTime() { @@ -37,6 +38,90 @@ function autoExpandTextarea(textarea) { 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, + '#$1' + ); +} + +// Функция для получения всех уникальных тегов из заметок +function getAllTags(notes) { + const tagCounts = {}; + + notes.forEach((note) => { + const tags = extractTags(note.content); + tags.forEach((tag) => { + tagCounts[tag] = (tagCounts[tag] || 0) + 1; + }); + }); + + return tagCounts; +} + +// Функция для отображения тегов +function renderTags() { + const tagsContainer = document.getElementById("tagsContainer"); + if (!tagsContainer) return; + + const tagCounts = getAllTags(allNotes); + const sortedTags = Object.keys(tagCounts).sort(); + + if (sortedTags.length === 0) { + tagsContainer.innerHTML = + '
Нет тегов
'; + return; + } + + tagsContainer.innerHTML = sortedTags + .map((tag) => { + const count = tagCounts[tag]; + const isActive = selectedTagFilter === tag ? "active" : ""; + return `#${tag}${count}`; + }) + .join(""); + + // Добавляем обработчики кликов для тегов + tagsContainer.querySelectorAll(".tag").forEach((tagElement) => { + tagElement.addEventListener("click", handleTagClick); + }); +} + +// Обработчик клика на тег +function handleTagClick(event) { + const clickedTag = event.target.closest(".tag").dataset.tag; + + // Если кликнули на тот же тег, снимаем фильтр + if (selectedTagFilter === clickedTag) { + selectedTagFilter = null; + } else { + selectedTagFilter = clickedTag; + } + + // Перерисовываем заметки и теги + renderNotes(allNotes); + renderTags(); + updateFilterIndicator(); +} + // Привязываем авторасширение к текстовому полю для создания заметки noteInput.addEventListener("input", function () { autoExpandTextarea(noteInput); @@ -201,6 +286,7 @@ async function loadNotes() { allNotes = notes; // Сохраняем все заметки в глобальную переменную renderNotes(notes); renderCalendar(); // Обновляем календарь после загрузки заметок + renderTags(); // Обновляем теги после загрузки заметок } catch (error) { console.error("Ошибка:", error); notesList.innerHTML = "

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

"; @@ -211,25 +297,44 @@ async function loadNotes() { function renderNotes(notes) { notesList.innerHTML = ""; - // Фильтруем заметки, если выбрана дата + // Фильтруем заметки по дате и тегам let notesToDisplay = notes; + if (selectedDateFilter) { - notesToDisplay = notes.filter((note) => note.date === 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) { - if (selectedDateFilter) { - notesList.innerHTML = `

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

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

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

'; + let message = "Заметок пока нет"; + + if (selectedDateFilter && selectedTagFilter) { + message = `Нет заметок за ${selectedDateFilter} с тегом #${selectedTagFilter}`; + } else if (selectedDateFilter) { + message = `Нет заметок за выбранную дату (${selectedDateFilter})`; + } else if (selectedTagFilter) { + message = `Нет заметок с тегом #${selectedTagFilter}`; } + + notesList.innerHTML = `

${message}

`; return; } // Итерируемся по заметкам в обычном порядке, чтобы новые были сверху notesToDisplay.forEach(function (note) { + // Преобразуем теги в кликабельные элементы перед парсингом markdown + const contentWithClickableTags = makeTagsClickable(note.content); + const parsedContent = marked.parse(contentWithClickableTags); + const noteHtml = `
@@ -244,7 +349,7 @@ function renderNotes(notes) {
${marked.parse(note.content)}
+ )}">${parsedContent}
`; notesList.insertAdjacentHTML("afterbegin", noteHtml); @@ -252,6 +357,9 @@ function renderNotes(notes) { // Добавляем обработчики событий для кнопок редактирования и удаления addNoteEventListeners(); + + // Добавляем обработчики кликов для тегов в заметках + addTagClickListeners(); } // Функция для добавления обработчиков событий к заметкам @@ -375,6 +483,30 @@ function addNoteEventListeners() { }); } +// Функция для добавления обработчиков кликов на теги в заметках +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() !== "") { @@ -630,9 +762,10 @@ function handleDayClick(event) { selectedDateFilter = clickedDate; } - // Перерисовываем заметки и календарь + // Перерисовываем заметки, календарь и теги renderNotes(allNotes); renderCalendar(); + renderTags(); updateFilterIndicator(); } @@ -641,9 +774,21 @@ 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 = `Фильтр: ${selectedDateFilter} `; + filterIndicator.innerHTML = `Фильтр: ${filters.join( + ", " + )} `; // Добавляем обработчик клика для кнопки сброса const clearBtn = document.getElementById("clear-filter-btn"); @@ -658,8 +803,10 @@ function updateFilterIndicator() { // Функция для сброса фильтра (глобальная) window.clearFilter = function () { selectedDateFilter = null; + selectedTagFilter = null; renderNotes(allNotes); renderCalendar(); + renderTags(); updateFilterIndicator(); }; diff --git a/public/notes.html b/public/notes.html index 6f08bcd..b1a4c8c 100644 --- a/public/notes.html +++ b/public/notes.html @@ -29,6 +29,16 @@
+ + +
+
+ 🏷️ Теги +
+
+ +
+
diff --git a/public/style.css b/public/style.css index eeddfb1..d674180 100644 --- a/public/style.css +++ b/public/style.css @@ -601,3 +601,80 @@ textarea:focus { .calendar-day.today.has-notes::after { background-color: #fff; } + +/* Стили для секции тегов */ +.tags-section { + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid #e0e0e0; +} + +.tags-header { + margin-bottom: 10px; +} + +.tags-title { + font-size: 12px; + font-weight: bold; + color: #333; +} + +.tags-container { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.tag { + display: inline-block; + padding: 4px 8px; + background-color: #e7f3ff; + color: #007bff; + border: 1px solid #007bff; + border-radius: 12px; + font-size: 10px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + user-select: none; +} + +.tag:hover { + background-color: #007bff; + color: white; +} + +.tag.active { + background-color: #007bff; + color: white; + font-weight: bold; +} + +.tag-count { + margin-left: 4px; + font-size: 9px; + opacity: 0.8; +} + +/* Стили для тегов в заметках */ +.textNote .tag-in-note { + display: inline-block; + padding: 2px 6px; + background-color: #e7f3ff; + color: #007bff; + border: 1px solid #007bff; + border-radius: 8px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + text-decoration: none; + margin: 0 2px; +} + +.textNote .tag-in-note:hover { + background-color: #007bff; + color: white; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3); +} diff --git a/tags_functionality_screenshot.png b/tags_functionality_screenshot.png new file mode 100644 index 0000000..1e58f38 Binary files /dev/null and b/tags_functionality_screenshot.png differ