🎉 Добавлен мобильный боковой слайдер с календарём, поиском и тегами
- Добавлена кнопка открытия слайдера (☰) на мобильных устройствах - Реализован удобный боковой слайдер для мобильной версии - Слайдер содержит полностью функциональный календарь с навигацией - Поле поиска синхронизировано с ПК версией - Теги отображаются с количеством заметок - Возможность закрытия слайдера кнопкой или оверлеем - Все функции работают как в ПК версии - Добавлена синхронизация между мобильным и ПК календарями - Обновлена документация README с описанием мобильной версии
This commit is contained in:
parent
810e309db8
commit
6f79afeb7e
28
README.md
28
README.md
@ -15,6 +15,8 @@
|
|||||||
- 🏷️ **Система тегов с автоматическим извлечением из заметок** (NEW!)
|
- 🏷️ **Система тегов с автоматическим извлечением из заметок** (NEW!)
|
||||||
- 🔍 **Поиск по заметкам с подсветкой результатов** (NEW!)
|
- 🔍 **Поиск по заметкам с подсветкой результатов** (NEW!)
|
||||||
- 📅 **Мини-календарь для навигации по датам заметок** (NEW!)
|
- 📅 **Мини-календарь для навигации по датам заметок** (NEW!)
|
||||||
|
- 📱 **Адаптивный дизайн с боковым слайдером на мобильных** (NEW!)
|
||||||
|
- ☰ **Мобильный боковой слайдер с календарём, поиском и тегами** (NEW!)
|
||||||
- 🎨 Простой и интуитивный интерфейс
|
- 🎨 Простой и интуитивный интерфейс
|
||||||
- 📱 Адаптивный дизайн
|
- 📱 Адаптивный дизайн
|
||||||
|
|
||||||
@ -128,6 +130,32 @@ npm start
|
|||||||
|
|
||||||
Нажмите кнопку "🚪 Выйти" в верхней части страницы заметок
|
Нажмите кнопку "🚪 Выйти" в верхней части страницы заметок
|
||||||
|
|
||||||
|
## Мобильная версия
|
||||||
|
|
||||||
|
### Боковой слайдер на мобильных устройствах
|
||||||
|
|
||||||
|
На мобильных устройствах (ширина экрана до 768 пикселей) вместо постоянной левой панели появляется удобный **боковой слайдер** с кнопкой "☰" в левом верхнем углу.
|
||||||
|
|
||||||
|
**Функциональность мобильного слайдера:**
|
||||||
|
|
||||||
|
1. **Кнопка открытия** - нажмите на кнопку "☰" в левом верхнем углу экрана
|
||||||
|
2. **Содержимое слайдера** включает все основные инструменты:
|
||||||
|
- 📅 **Календарь** - полностью функциональный календарь месяца с навигацией
|
||||||
|
- 🔍 **Поле поиска** - синхронизировано с основным полем поиска
|
||||||
|
- 🏷️ **Теги** - все теги с количеством заметок
|
||||||
|
3. **Закрытие слайдера**:
|
||||||
|
- Нажмите кнопку ✕ в верхнем правом углу слайдера
|
||||||
|
- Нажмите на серую область (оверлей) справа
|
||||||
|
4. **Синхронизация** - изменения в слайдере (выбор даты, ввод текста, клик на тег) автоматически синхронизируются с основной ПК версией
|
||||||
|
|
||||||
|
**Преимущества мобильного слайдера:**
|
||||||
|
|
||||||
|
- ✅ Полная функциональность как в ПК версии
|
||||||
|
- ✅ Экономия места на экране мобильного устройства
|
||||||
|
- ✅ Легкое открытие/закрытие
|
||||||
|
- ✅ Удобная навигация по заметкам
|
||||||
|
- ✅ Не блокирует контент при открытии (использует оверлей)
|
||||||
|
|
||||||
## Структура проекта
|
## Структура проекта
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
364
public/app.js
364
public/app.js
@ -1116,3 +1116,367 @@ function initSearch() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== МОБИЛЬНЫЙ СЛАЙДЕР ====================
|
||||||
|
|
||||||
|
// Функция для отображения календаря в мобильном слайдере
|
||||||
|
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", handleDayClickMobile);
|
||||||
|
|
||||||
|
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", handleDayClickMobile);
|
||||||
|
|
||||||
|
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", handleDayClickMobile);
|
||||||
|
|
||||||
|
calendarDays.appendChild(dayDiv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик клика на день в календаре для мобильной версии
|
||||||
|
function handleDayClickMobile(event) {
|
||||||
|
const clickedDate = event.target.dataset.date;
|
||||||
|
|
||||||
|
// Если кликнули на тот же день, снимаем фильтр
|
||||||
|
if (selectedDateFilter === clickedDate) {
|
||||||
|
selectedDateFilter = null;
|
||||||
|
} else {
|
||||||
|
selectedDateFilter = clickedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перерисовываем заметки, оба календаря и теги
|
||||||
|
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 =
|
||||||
|
'<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", handleTagClickMobile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик клика на тег в мобильном слайдере
|
||||||
|
function handleTagClickMobile(event) {
|
||||||
|
const clickedTag = event.target.closest(".tag").dataset.tag;
|
||||||
|
|
||||||
|
// Если кликнули на тот же тег, снимаем фильтр
|
||||||
|
if (selectedTagFilter === clickedTag) {
|
||||||
|
selectedTagFilter = null;
|
||||||
|
} else {
|
||||||
|
selectedTagFilter = clickedTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перерисовываем заметки, теги и оба календаря
|
||||||
|
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", 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";
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,76 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iconify/2.0.0/iconify.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/iconify/2.0.0/iconify.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Кнопка открытия слайдера для мобильной версии -->
|
||||||
|
<div class="mobile-menu-btn" id="mobileMenuBtn">
|
||||||
|
<span class="iconify" data-icon="mdi:menu"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Слайдер для мобильной версии -->
|
||||||
|
<div class="mobile-sidebar" id="mobileSidebar">
|
||||||
|
<div class="sidebar-close-btn" id="sidebarCloseBtn">
|
||||||
|
<span class="iconify" data-icon="mdi:close"></span>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-content">
|
||||||
|
<div class="mini-calendar">
|
||||||
|
<div class="calendar-header">
|
||||||
|
<button class="calendar-nav" id="prevMonthMobile">‹</button>
|
||||||
|
<span class="calendar-month-year" id="monthYearMobile"></span>
|
||||||
|
<button class="calendar-nav" id="nextMonthMobile">›</button>
|
||||||
|
</div>
|
||||||
|
<div class="calendar-weekdays">
|
||||||
|
<div class="calendar-weekday">Пн</div>
|
||||||
|
<div class="calendar-weekday">Вт</div>
|
||||||
|
<div class="calendar-weekday">Ср</div>
|
||||||
|
<div class="calendar-weekday">Чт</div>
|
||||||
|
<div class="calendar-weekday">Пт</div>
|
||||||
|
<div class="calendar-weekday">Сб</div>
|
||||||
|
<div class="calendar-weekday">Вс</div>
|
||||||
|
</div>
|
||||||
|
<div class="calendar-days" id="calendarDaysMobile"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Секция поиска -->
|
||||||
|
<div class="search-section">
|
||||||
|
<div class="search-header">
|
||||||
|
<span class="search-title"
|
||||||
|
><span class="iconify" data-icon="mdi:magnify"></span> Поиск</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="search-container">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="searchInputMobile"
|
||||||
|
placeholder="Поиск по заметкам..."
|
||||||
|
class="search-input"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
id="clearSearchBtnMobile"
|
||||||
|
class="clear-search-btn"
|
||||||
|
style="display: none"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Секция тегов -->
|
||||||
|
<div class="tags-section">
|
||||||
|
<div class="tags-header">
|
||||||
|
<span class="tags-title"
|
||||||
|
><span class="iconify" data-icon="mdi:tag"></span> Теги</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="tags-container" id="tagsContainerMobile">
|
||||||
|
<!-- Теги будут добавлены динамически -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Оверлей для закрытия слайдера -->
|
||||||
|
<div class="mobile-sidebar-overlay" id="sidebarOverlay"></div>
|
||||||
|
|
||||||
<div class="container-leftside">
|
<div class="container-leftside">
|
||||||
<div class="mini-calendar">
|
<div class="mini-calendar">
|
||||||
<div class="calendar-header">
|
<div class="calendar-header">
|
||||||
|
|||||||
104
public/style.css
104
public/style.css
@ -896,8 +896,111 @@ textarea:focus {
|
|||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стили для мобильного меню и слайдера */
|
||||||
|
.mobile-menu-btn {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 15px;
|
||||||
|
left: 15px;
|
||||||
|
z-index: 999;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-btn:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-btn .iconify {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Мобильный слайдер */
|
||||||
|
.mobile-sidebar {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: -300px;
|
||||||
|
width: 280px;
|
||||||
|
height: 100vh;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1000;
|
||||||
|
overflow-y: auto;
|
||||||
|
transition: left 0.3s ease;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-sidebar.open {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-close-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 10px 10px 10px auto;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-close-btn:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-close-btn .iconify {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content {
|
||||||
|
padding: 10px 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Оверлей для закрытия слайдера */
|
||||||
|
.mobile-sidebar-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 999;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-sidebar-overlay.open {
|
||||||
|
display: block;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Мобильная адаптация */
|
/* Мобильная адаптация */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
/* Показываем мобильное меню */
|
||||||
|
.mobile-menu-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Скрываем левый блок с календарем, поиском и тегами */
|
/* Скрываем левый блок с календарем, поиском и тегами */
|
||||||
.container-leftside {
|
.container-leftside {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
@ -917,6 +1020,7 @@ textarea:focus {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
margin-top: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Адаптируем контейнер заметок */
|
/* Адаптируем контейнер заметок */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user