Files
roi-theme/assets/js/notification-bar.js
FrankZamora de5fff4f5c Fase 1: Estructura Base y DI Container - Clean Architecture
COMPLETADO: Fase 1 de la migración a Clean Architecture + POO

## Estructura de Carpetas
- ✓ Estructura completa de 4 capas (Domain, Application, Infrastructure, Presentation)
- ✓ Carpetas de Use Cases (SaveComponent, GetComponent, DeleteComponent, SyncSchema)
- ✓ Estructura de tests (Unit, Integration, E2E)
- ✓ Carpetas de schemas y templates

## Composer y Autoloading
- ✓ PSR-4 autoloading configurado para ROITheme namespace
- ✓ Autoloader optimizado regenerado

## DI Container
- ✓ DIContainer implementado con patrón Singleton
- ✓ Métodos set(), get(), has() para gestión de servicios
- ✓ Getters específicos para ComponentRepository, ValidationService, CacheService
- ✓ Placeholders que serán implementados en Fase 5
- ✓ Prevención de clonación y deserialización

## Interfaces
- ✓ ComponentRepositoryInterface (Domain)
- ✓ ValidationServiceInterface (Application)
- ✓ CacheServiceInterface (Application)
- ✓ Component entity placeholder (Domain)

## Bootstrap
- ✓ functions.php actualizado con carga de Composer autoloader
- ✓ Inicialización del DIContainer
- ✓ Helper function roi_container() disponible globalmente

## Tests
- ✓ 10 tests unitarios para DIContainer (100% cobertura)
- ✓ Total: 13 tests unitarios, 28 assertions
- ✓ Suite de tests pasando correctamente

## Validación
- ✓ Script de validación automatizado (48/48 checks pasados)
- ✓ 100% de validaciones exitosas

La arquitectura base está lista para la Fase 2.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 13:48:24 -06:00

149 lines
4.8 KiB
JavaScript

/**
* Top Notification Bar Script
* Issue #39
*
* Maneja el cierre de la barra de notificación y el almacenamiento
* de la preferencia del usuario mediante cookies.
*
* @package ROI_Theme
* @since 1.0.0
*/
(function() {
'use strict';
/**
* Inicialización cuando el DOM está listo
*/
document.addEventListener('DOMContentLoaded', function() {
initNotificationBar();
});
/**
* Inicializa la funcionalidad de la barra de notificación
*/
function initNotificationBar() {
const notificationBar = document.getElementById('topNotificationBar');
const closeBtn = document.querySelector('.btn-close-notification');
const navbar = document.querySelector('.navbar');
// Verificar que existan los elementos necesarios
if (!notificationBar || !closeBtn) {
return;
}
// Event listener para el botón de cerrar
closeBtn.addEventListener('click', function() {
closeNotificationBar(notificationBar, navbar);
});
// Permitir cerrar con la tecla Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && notificationBar.style.display !== 'none') {
closeNotificationBar(notificationBar, navbar);
}
});
// Ajustar el scroll inicial si la barra está visible
adjustInitialScroll(notificationBar);
}
/**
* Cierra la barra de notificación con animación
*
* @param {HTMLElement} notificationBar - Elemento de la barra de notificación
* @param {HTMLElement} navbar - Elemento del navbar
*/
function closeNotificationBar(notificationBar, navbar) {
// Ocultar barra con animación de slide up
notificationBar.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
notificationBar.style.transform = 'translateY(-100%)';
notificationBar.style.opacity = '0';
// Esperar a que termine la animación antes de ocultar completamente
setTimeout(function() {
notificationBar.style.display = 'none';
document.body.classList.add('notification-dismissed');
// Ajustar navbar suavemente
if (navbar) {
navbar.style.transition = 'top 0.3s ease';
navbar.style.top = '0';
}
// Guardar cookie por 7 días
setCookie('roinotification_dismissed', '1', 7);
// Disparar evento personalizado para otros scripts
const event = new CustomEvent('notificationBarClosed', {
detail: { timestamp: Date.now() }
});
document.dispatchEvent(event);
}, 300);
}
/**
* Establece una cookie con nombre, valor y días de expiración
*
* @param {string} name - Nombre de la cookie
* @param {string} value - Valor de la cookie
* @param {number} days - Días hasta la expiración
*/
function setCookie(name, value, days) {
const expiryDate = new Date();
expiryDate.setDate(expiryDate.getDate() + days);
const cookie = name + '=' + encodeURIComponent(value) +
'; expires=' + expiryDate.toUTCString() +
'; path=/' +
'; SameSite=Lax';
document.cookie = cookie;
}
/**
* Ajusta el scroll inicial para compensar la altura de la barra
*
* @param {HTMLElement} notificationBar - Elemento de la barra de notificación
*/
function adjustInitialScroll(notificationBar) {
// Si hay un hash en la URL, reajustar el scroll para compensar la altura
if (window.location.hash) {
setTimeout(function() {
const target = document.querySelector(window.location.hash);
if (target) {
const barHeight = notificationBar.offsetHeight;
const navbarHeight = document.querySelector('.navbar')?.offsetHeight || 0;
const offset = barHeight + navbarHeight;
window.scrollTo({
top: target.offsetTop - offset,
behavior: 'smooth'
});
}
}, 100);
}
}
/**
* Función helper para obtener el valor de una cookie
*
* @param {string} name - Nombre de la cookie
* @return {string|null} - Valor de la cookie o null si no existe
*/
function getCookie(name) {
const nameEQ = name + '=';
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.indexOf(nameEQ) === 0) {
return decodeURIComponent(cookie.substring(nameEQ.length));
}
}
return null;
}
})();