Corp/backend/templates/base.html
Игорь Надеин d36e9dfd7c feat: Auto-unsubscribe push on logout, force resubscribe on login
- Добавлена автоматическая отписка от Web Push при выходе
- При входе принудительно переподписываем пользователя
- Очищаем старую подписку из localStorage перед новой
- Это исправляет AbortError из-за старых подписок с другими VAPID ключами
2026-01-06 08:11:24 +03:00

179 lines
8.1 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.

{% load static api_tags %}
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="color-scheme" content="light dark">
{% if user.is_authenticated %}
<meta name="api-access" content="{% api_access_token request %}">
<meta name="user-id" content="{{ user.id }}">
{% endif %}
{# КРИТИЧНО: Применяем тему ДО загрузки CSS для предотвращения FOUC #}
<script src="{% static 'js/utils/themeInitializer.js' %}"></script>
<title>{% block title %}HiRo{% endblock %}</title>
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'img/favicon-32.png' %}">
<link rel="apple-touch-icon" href="{% static 'img/apple-touch-icon.png' %}">
<link rel="icon" type="image/png" sizes="192x192" href="{% static 'img/logo-192.png' %}">
{# Preload критичных CSS для ускорения загрузки #}
<link rel="preload" href="{% static 'css/bootstrap-custom.css' %}" as="style">
<link rel="preload" href="{% static 'css/variables.css' %}" as="style">
<link rel="preload" href="{% static 'css/components/common.css' %}" as="style">
<!-- Bootstrap / Icons / FullCalendar -->
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"> -->
<!-- Кастомный Bootstrap с нашей системой отступов -->
<link rel="stylesheet" href="{% static 'css/bootstrap-custom.css' %}">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<!-- CSS Components -->
<link rel="stylesheet" href="{% static 'css/variables.css' %}">
<link rel="stylesheet" href="{% static 'css/components/common.css' %}">
<link rel="stylesheet" href="{% static 'css/components/spacing-utils.css' %}">
<link rel="stylesheet" href="{% static 'css/components/layout-spacing.css' %}">
<link rel="stylesheet" href="{% static 'css/components/buttons.css' %}">
<link rel="stylesheet" href="{% static 'css/components/ios-search.css' %}">
<link rel="stylesheet" href="{% static 'css/components/base-app.css' %}">
<link rel="stylesheet" href="{% static 'css/components/ios-components.css' %}">
<link rel="stylesheet" href="{% static 'css/components/card-list.css' %}">
<link rel="stylesheet" href="{% static 'css/notifications/notifications.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body class="min-vh-100">
{# Устанавливаем глобальные переменные в самом начале #}
{% if user.is_authenticated %}
<script>
window.currentUserId = {{ user.id }};
</script>
{% endif %}
{% include "includes/navbar.html" %}
<div class="container-xxl">
<div class="app-layout {% if user.is_authenticated %}with-sidebar with-rightbar{% endif %}">
{% if user.is_authenticated %}
{% include "includes/sidebar.html" %}
{% endif %}
<main class="main">
{% block content %}{% endblock %}
</main>
{% if user.is_authenticated %}
{% include "includes/rightbar_calendar.html" %}
{% endif %}
</div>
</div>
<!-- JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous" defer></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/@fullcalendar/core@6.1.8/locales-all.global.min.js" defer></script>
{# Универсальный модуль для автоматического расширения textarea #}
<script src="{% static 'js/components/textareaAutoExpand.js' %}" defer></script>
{# Импорт утилит из модулей #}
<script type="module">
import { esc, norm, escAttr, truncate, getInitials } from '{% static "js/utils/stringUtils.js" %}';
import { debounce, throttle, delay } from '{% static "js/utils/timing.js" %}';
import { smoothScrollTo, scrollToTop, isElementInViewport } from '{% static "js/utils/scroll.js" %}';
import { initNavbarHeight } from '{% static "js/components/navbarHeight.js" %}';
import { initLikeHandler } from '{% static "js/components/likeHandler.js" %}';
// Экспортируем в window для обратной совместимости с существующим кодом
window.esc = esc;
window.norm = norm;
window.escAttr = escAttr;
window.truncate = truncate;
window.getInitials = getInitials;
window.debounce = debounce;
window.throttle = throttle;
window.delay = delay;
window.smoothScrollTo = smoothScrollTo;
window.scrollToTop = scrollToTop;
window.isElementInViewport = isElementInViewport;
// Инициализация компонентов
document.addEventListener('DOMContentLoaded', () => {
const navbarHeight = initNavbarHeight();
const likeHandler = initLikeHandler();
if (navbarHeight) window.navbarHeight = navbarHeight;
if (likeHandler) window.likeHandler = likeHandler;
});
</script>
{# ========== UNIVERSAL USER WEBSOCKET ========== #}
{# Универсальное WebSocket-подключение для всех real-time обновлений #}
{% if user.is_authenticated %}
<script type="module">
import { initUserWebSocket } from "{% static 'js/components/userWebSocket.js' %}";
// Базовая инициализация WebSocket (без chat-специфичной логики)
const userWs = initUserWebSocket({
userId: {{ user.id }}
});
// Экспортируем в window для доступа из других модулей
if (userWs) {
window.userWebSocket = userWs;
window.dispatchEvent(new CustomEvent('user:ws-ready', {
detail: { api: userWs, ws: userWs.ws }
}));
}
</script>
<!-- Notification Manager -->
<script type="module" src="{% static 'js/notifications/notification-manager.js' %}"></script>
<!-- Web Push Notifications -->
<script type="module">
import { pushNotifications } from "{% static 'js/notifications/push-notifications.js' %}";
// Инициализация Web Push после загрузки страницы
document.addEventListener('DOMContentLoaded', async () => {
const initialized = await pushNotifications.init();
if (initialized) {
console.log('[WebPush] Инициализирован, подписка:', pushNotifications.isSubscribed);
// Если разрешение дано - всегда переподписываем при входе
// Это гарантирует, что подписка привязана к текущему пользователю
if (pushNotifications.getPermissionStatus() === 'granted') {
console.log('[WebPush] Разрешение есть, переподписываемся...');
// Очищаем старый endpoint из localStorage
localStorage.removeItem('push_endpoint');
// Подписываемся (метод subscribe сам удалит старую подписку)
const subscribed = await pushNotifications.subscribe();
if (subscribed) {
console.log('[WebPush] ✅ Успешно подписан');
} else {
console.warn('[WebPush] ⚠️ Не удалось подписаться');
}
} else if (pushNotifications.getPermissionStatus() === 'default') {
console.log('[WebPush] Пользователь еще не дал разрешение на уведомления');
}
}
});
</script>
{% endif %}
{% block extra_js %}{% endblock %}
</body>
</html>