avtopost.kultmost.ru/templates/components/header.html
2026-08-02 12:08:08 -04:00

360 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

{% macro section(user) -%}
<script>
function toggleMenu() {
const button = document.getElementById('menu_button');
const lines = button.querySelectorAll('.line');
lines.forEach((line) => {
line.classList.toggle('active');
});
}
document.addEventListener('DOMContentLoaded', () => {
const btn = document.getElementById('menu_button');
const menu = document.getElementById('menu');
const menu2 = document.getElementById('menu2');
if (!btn) return;
btn.addEventListener('click', () => {
toggleMenu();
const isOpen = btn.getAttribute('aria-expanded') === 'true';
const newState = !isOpen;
btn.setAttribute('aria-expanded', String(newState));
menu.classList.toggle('open', newState);
menu2.classList.toggle('open', newState);
});
});
</script>
{% if user %}
{% if user.notify %}
<script>
async function removeShow() {
document.getElementById('notificationsDropdown').classList.remove('show');
document.getElementById('closeNotify').classList.remove('show');
document.getElementById('notificationsBtn').setAttribute('aria-expanded', 'false');
}
async function showNotify() {
const isOpen = document.getElementById('notificationsDropdown').classList.contains('show');
const newState = !isOpen;
document.getElementById('notificationsDropdown').classList.toggle('show', newState);
document.getElementById('closeNotify').classList.toggle('show', newState);
document.getElementById('notificationsBtn').setAttribute('aria-expanded', String(newState));
}
document.addEventListener('DOMContentLoaded', () => {
// Function to update notifications after marking them as read
function updateNotifications(updatedData) {
document.querySelectorAll('#notificationCount').forEach(elem => elem.style.display = 'none');
const notificationsList = document.getElementById('notificationsList');
notificationsList.innerHTML = '';
notificationsArray.forEach(item => {
addNotification(
item.title,
item.text,
item.dateStr,
item.link,
false
);
});
}
document.getElementById('markAsReadBtn').addEventListener('click', function() {
fetch('/api/read/notify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
showToast({ message: 'Ошибка', status: 'error' });
throw new Error('Network response was not ok');
}
return response.json().then(data => {
notificationsArray.forEach(item => item.read = true);
updateNotifications();
});
})
.catch(error => {
const errorText = error.message || 'Произошла ошибка';
showToast({ message: errorText, status: 'error' });
});
});
// === Открытие уведомлений — так же, как меню ===
const notifyBtn = document.getElementById('notificationsBtn');
const dropdown = document.getElementById('notificationsDropdown');
const closeOverlay = document.getElementById('closeNotify');
// Клик по кнопке уведомлений
notifyBtn.addEventListener('click', (e) => {
e.stopPropagation();
// showNotify();
});
// Клик внутри дропдауна не закрывает
dropdown.addEventListener('click', (e) => {
e.stopPropagation();
});
function addNotification(title, text, time, link = '#', unread = true) {
const list = document.getElementById('notificationsList');
const itemLink = document.createElement('a');
itemLink.href = link + '#main';
const item = document.createElement('div');
item.className = `notification-item ${unread ? 'unread' : ''}`;
item.innerHTML = `
<div>
<strong>${title}</strong>
<p>${text}</p>
<small>${time}</small>
</div>
`;
itemLink.prepend(item);
list.prepend(itemLink);
}
// Initial load of notifications
const notificationsArray = [];
{% for item in user.notify %}
notificationsArray.push({
title: "{{ item.title | e }}",
text: "{{ item.text | e }}",
dateStr: "{{ (item._id.generation_time.strftime('%d.%m.%Y')) if item._id else '' }}",
link: "{{ item.link | e }}",
read: Boolean({{ 1 if item.read else 0 }})
});
{% endfor %}
function parseDate(dateStr) {
if (!dateStr) return null;
const [dd, mm, yyyy] = dateStr.split('.');
const d = new Date(Number(yyyy), Number(mm) - 1, Number(dd));
return isNaN(d.getTime()) ? null : d;
}
notificationsArray.sort((a, b) => {
// 1) unread first
const aRead = a.read ? 1 : 0;
const bRead = b.read ? 1 : 0;
if (aRead !== bRead) return aRead - bRead;
// 2) newest first
const aTime = parseDate(a.dateStr)?.getTime() ?? 0;
const bTime = parseDate(b.dateStr)?.getTime() ?? 0;
return bTime - aTime;
});
notificationsArray.forEach(item => {
addNotification(item.title, item.text, item.dateStr, item.link, item.read);
});
});
</script>
{% endif %}
{% endif %}
<header class="c_xirl1">
<div class="c_uh9b6">
<a class="c_7dj6c" href="/">
<img src="/static/logo375.jpeg" alt="logo avtopost kultmost" class="c_vnonn b_cj7sg" crossorigin="anonymous" fetchpriority="high">
</a>
<div class="avatar">
{% if user %}
<a class="c_n2zau" href="/profile">
<img src="/a/{{user._id}}" class="b_e59wr" crossorigin="anonymous" fetchpriority="high">
</a>
{% endif %}
<button id="menu_button" class="c_pcz1l b_oxx99" aria-expanded="false">
<span class="nav-icon">
<span class="line line1"></span>
<span class="line line2"></span>
<span class="line line3"></span>
</span>
</button>
</div>
</div>
<nav id="menu" class="c_olmk1">
<a class="c_qmp95 b_fokm1" href="/">Главная</a>
<a class="c_qmp95 b_fokm1" href="https://kultmost.ru/post">Маркетплейс</a>
{% if user %}
<a class="c_qmp95 b_fokm1" href="/my-paints">Мои картины</a>
{% endif %}
{% if user and 'users:read' in user.get('scopes',[]) %}
<a class="c_qmp95 b_fokm1" href="/artists">Художники</a>
{% endif %}
<a class="c_qmp95 b_fokm1" href="/partner">Партнеры</a>
<a class="c_qmp95 b_fokm1" href="/about">О платформе</a>
<a class="c_qmp95 b_fokm1" href="/news">Новости</a>
<a class="c_qmp95 b_fokm1 mob" href="/profile">Личный кабинет</a>
{% if user and 'admin' in user.get('roles',[]) %}
<a class="c_qmp95 b_fokm1 btn" href="/admin">Админ-панель</a>
{% endif %}
{% if user and 'back' in user.get('scopes',[]) %}
<span id="backBtn" class="c_qmp95 b_fokm1 btn">Переключиться на Админа</span>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('backBtn').addEventListener('click', async () => {
try {
const resp = await fetch('/api/admin/back', { method: 'POST' });
if (!resp.ok) {
const text = await resp.text();
console.error('Server error:', resp.status, text);
return;
}
window.location.href = '/profile';
} catch (err) {
console.error('Network error:', err);
}
});
});
</script>
{% endif %}
<span
id="shareBtn" data-imagetext="🎨 Рекомендую! 👍"
data-image="/static/share.png" data-link="/" data-text="Рекомендую!"
class="c_qmp95 b_fokm1 mob shareService">Поделиться сервисом</span>
{% if user is none %}
<div class="mob auth">
<a href="/registration">Регистрация</a>
<span>/</span>
<a href="/login">Вход</a>
</div>
{% endif %}
</nav>
{% if user %}
<a class="c_n2zau" id="menu2" href="/profile">
<img src="/a/{{user._id}}" class="c_27foz b_e59wr" crossorigin="anonymous" fetchpriority="high">
<div class="c_kl46b b_qel18">
<span class="c_poz0x b_zp47n">{{user.username}}</span>
<span class="c_poz0x b_zp47n">Личный кабинет</span>
</div>
</a>
{% else %}
<div class="auth" id="menu2">
<a href="/registration">Регистрация</a>
<span>/</span>
<a href="/login">Вход</a>
</div>
{% endif %}
{% if user %}
{% if user.notify %}
<div class="header-right">
<div class="notifications-wrapper">
<button class="notifications-btn" id="notificationsBtn" aria-expanded="false" onclick="showNotify()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M15 17H9C9 18.6569 10.3431 20 12 20C13.6569 20 15 18.6569 15 17Z" fill="#333"/>
<path d="M18 8C18 6.4087 17.3679 4.88258 16.2426 3.75736C15.1174 2.63214 13.5913 2 12 2C10.4087 2 8.88258 2.63214 7.75736 3.75736C6.63214 4.88258 6 6.4087 6 8C6 11.0902 5.22047 13.206 4.34966 14.6054C3.87894 15.4286 3.5 16.5 3.5 17.5H20.5C20.5 16.5 20.1211 15.4286 19.6503 14.6054C18.7795 13.206 18 11.0902 18 8Z" stroke="#333" stroke-width="2"/>
</svg>
{% set notify = user.notify | selectattr('read') | list %}
{% if notify | length > 0 %}
<span class="notification-badge" id="notificationCount">{{ notify | length }}</span>
{% endif %}
</button>
<div>
<div id="closeNotify" class="closeNotify" onclick="showNotify()"></div>
<div class="notifications-dropdown" id="notificationsDropdown">
<div class="dropdown-header">
<strong>Уведомления</strong>
<button class="c_qmp95 b_fokm1 btn" id="markAsReadBtn">Отметить все как прочитанные</button>
</div>
<div class="notifications-list" id="notificationsList">
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endif %}
</header>
{%- endmacro %}
{% macro style() -%}
<style>
.nav-icon {display: flex;flex-direction: column;justify-content: space-around;height: 24px;cursor: pointer;position: relative;width: 100%;}
.line {right: 0;display: block;width: 42px;height: 0;transition: all 0.3s ease;position: absolute;border: 1px solid;}
.line1{top:3px}
.line2{width: 32px;top: 10px;}
.line3{top: 18px;}
.line1.active {max-width: 30px;top: 9px;transform: rotate(135deg);}
.line2.active {transform: translateX(-500px);}
.line3.active {max-width: 30px;top: 9px;transform: rotate(-135deg);}
</style>
<style>/* Base (common) styles */
.closeNotify{display: none;position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
}
.closeNotify.show {display: block;z-index: 2;}
.notifications-wrapper {position: relative;}
.notifications-btn {background: none;border: none;padding: 8px;cursor: pointer;position: relative}
.notification-badge {position: absolute;top: 0;right: 4px;background: #e74c3c;color: white;font-size: 11px;font-weight: bold;min-width: 16px;height: 16px;border-radius: 50%;display: flex;align-items: center;justify-content: center;padding: 0 4px;}
.notifications-dropdown {position: absolute;top: 100%;right: 0;width: 380px;background: white;border-radius: 12px;box-shadow: 0 10px 30px rgba(0,0,0,0.15);display: none;z-index: 2000;margin-top: 8px;overflow: hidden;}
.notifications-dropdown.show {display: block;z-index: 500;}
.dropdown-header {padding: 16px 20px;border-bottom: 1px solid #eee;display: flex;justify-content: space-between;align-items: center;gap: 15px;}
.notifications-list {max-height: 420px;overflow-y: auto;}
.notification-item {padding: 14px 20px;border-bottom: 1px solid #f0f0f0;transition: background 0.2s;color: #1F1F1F;}
.notification-item:hover {background: #f8f9fa;}
.notification-item.unread {background: #f0f7ff;}
.b_cj7sg { align-self: stretch; aspect-ratio: 127/84; width: 127px; }
.b_2jukj { fill: #1F1F1F; height: 12px; width: 18px; }
.b_dhbqs { flex-shrink: 0; height: 24px; width: 24px; }
.b_oxx99 { align-items: center; display: flex; height: 48px; justify-content: center; width: 48px; }
.b_fokm1 { display: flex; font-family: Inter; font-size: 16px; font-style: normal; font-weight: 400; line-height: normal; text-decoration-skip-ink: auto; text-decoration-style: solid; text-decoration-thickness: auto; text-underline-offset: auto; text-underline-position: from-font; }
.b_e59wr { border-radius: 1em; height: 70px; min-width: 70px; max-width: 170px; object-fit: cover; object-position: top; }
.b_zp47n { align-self: stretch; color: #5F1798; font-family: Inter; font-size: 16px; font-style: normal; font-weight: 400; line-height: normal; max-width: 210px; }
.b_qel18 { align-items: flex-start; display: flex; flex-direction: column; gap: 5px; justify-content: center; }
.avatar{display: flex;align-items: center;gap: 22px;}
nav>a{color: #111;}
.auth>a, .auth>span{color: #5f1798; font-size: 20px;}
.auth{display: flex;gap: 5px;}
.mob{display: none;}
/* Mobile */
@media (max-width: 699px) {
#menu, #menu2 { display: none; }
#menu.open{ display: flex; }
.c_qmp95{font-size: 20px;}
.mob{display: flex;}
.c_vnonn { aspect-ratio: 3/2; flex-shrink: 0; height: 70px; width: 105px; }
.c_7dj6c { align-items: center; display: flex; height: 70px; justify-content: center; width: 105px; }
.c_uh9b6 { align-items: center; background: #FFF; display: flex; flex: 1 0 0; justify-content: space-between; }
.shareService{border-radius: 5px;background: #f39318;color: #111;padding: 13px 15px;justify-content: center;align-items: center;display: flex;margin: 5px 0px;font-size: 16px;}
.c_olmk1 {position: fixed;top: 0;left: 0;width: 300px;flex-direction: column;gap: 5px;background: white;bottom: 0;z-index: 1111;border: 1px solid #5f1798;padding: 50px 20px; }
.c_poz0x { align-self: stretch; color: #5F1798; font-family: Inter; font-size: 16px; font-style: normal; font-weight: 400; line-height: normal; min-width: 255px; }
.c_n2zau { align-items: center; display: flex; flex: 1 0 0; gap: 20px; max-width: 345px;}
.c_xirl1 { align-content: center; align-items: center; align-self: stretch; background: #FFF; display: flex; flex-wrap: wrap; justify-content: space-between; padding: 20px 15px; row-gap: 20px; }
}
/* Tablet */
@media (min-width: 700px) and (max-width: 900px) {
#menu, #menu2 { display: none; }
#menu.open { display: flex; }
.c_qmp95{font-size: 20px;}
.c_7dj6c { align-items: center; display: flex; flex: 1 0 0; height: 84px; }
.c_uh9b6 { align-items: center; background: #FFF; display: flex; flex: 1 0 0; justify-content: center; }
.c_olmk1 {position: fixed;top: 0;left: 0;width: 300px;flex-direction: column;gap: 5px;background: white;bottom: 0;z-index: 1111;border: 1px solid #5f1798;padding: 50px 20px; }
.c_n2zau { align-items: center; display: flex; flex: 1 0 0; gap: 20px; }
.c_xirl1 { align-content: center; align-items: center; align-self: stretch; background: #FFF; display: flex; flex-wrap: wrap; justify-content: space-between; padding: 20px; row-gap: 20px; }
}
/* Desktop */
@media (min-width: 901px) {
.avatar,#menu_button{display: none;}
.c_7dj6c { align-items: center; align-self: stretch; display: flex; height: 84px; justify-content: center; }
.c_8mapk { fill: #1F1F1F; flex-shrink: 0; height: 12px; width: 18px; }
.c_vb7zi { align-items: center; display: flex; flex-shrink: 0; height: 24px; justify-content: center; padding: 6px 3px; width: 24px; }
.c_uh9b6 { align-items: flex-start; display: flex; flex-direction: column; width: 139px; }
.c_olmk1 { align-content: center; align-items: center; display: flex; flex-wrap: wrap; gap: 5px 20px; }
.c_n2zau { align-items: center; display: flex; gap: 20px; }
.c_xirl1 { align-content: center; align-items: center; align-self: stretch; background: #FFF; display: flex; flex-wrap: wrap; justify-content: space-between; padding: 20px 100px; row-gap: 20px; }
}
.btn{border-radius: 5px;background: #5F1798;color: white;padding: 13px 15px;justify-content: center;align-items: center;display: flex;margin: 5px 0px;font-size: 16px;}
</style>
{%- endmacro %}