150 lines
6.8 KiB
JavaScript
150 lines
6.8 KiB
JavaScript
|
|
/**
|
||
|
|
* @fileoverview Application Header Component for Rantii
|
||
|
|
* @author retoor <retoor@molodetz.nl>
|
||
|
|
* @description Main navigation header with search and user controls
|
||
|
|
* @keywords header, navigation, search, menu, toolbar
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { BaseComponent } from './base-component.js';
|
||
|
|
|
||
|
|
class AppHeader extends BaseComponent {
|
||
|
|
static get observedAttributes() {
|
||
|
|
return ['logged-in'];
|
||
|
|
}
|
||
|
|
|
||
|
|
init() {
|
||
|
|
this.render();
|
||
|
|
this.bindEvents();
|
||
|
|
}
|
||
|
|
|
||
|
|
render() {
|
||
|
|
const isLoggedIn = this.isLoggedIn();
|
||
|
|
const user = this.getCurrentUser();
|
||
|
|
|
||
|
|
this.setHtml(`
|
||
|
|
<div class="header-container">
|
||
|
|
<div class="header-left">
|
||
|
|
<button class="menu-toggle" aria-label="Toggle menu">
|
||
|
|
<span class="menu-icon"></span>
|
||
|
|
</button>
|
||
|
|
<a href="?" class="logo">
|
||
|
|
<span class="logo-text">Rantii</span>
|
||
|
|
</a>
|
||
|
|
</div>
|
||
|
|
<div class="header-center">
|
||
|
|
<div class="search-container">
|
||
|
|
<input type="search" class="search-input" placeholder="Search rants..." aria-label="Search">
|
||
|
|
<button class="search-btn" aria-label="Search">
|
||
|
|
<svg viewBox="0 0 24 24" width="20" height="20">
|
||
|
|
<path fill="currentColor" d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 0 0 1.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 0 0-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 0 0 5.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="header-right">
|
||
|
|
${isLoggedIn ? `
|
||
|
|
<button class="header-btn notifications-btn" aria-label="Notifications">
|
||
|
|
<svg viewBox="0 0 24 24" width="24" height="24">
|
||
|
|
<path fill="currentColor" d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z"/>
|
||
|
|
</svg>
|
||
|
|
<span class="notification-badge" hidden>0</span>
|
||
|
|
</button>
|
||
|
|
<button class="header-btn post-btn" aria-label="Create post">
|
||
|
|
<svg viewBox="0 0 24 24" width="24" height="24">
|
||
|
|
<path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
<button class="header-btn user-btn" aria-label="User menu">
|
||
|
|
<user-avatar size="small" username="${user?.username || ''}"></user-avatar>
|
||
|
|
</button>
|
||
|
|
` : `
|
||
|
|
<button class="header-btn login-btn">Login</button>
|
||
|
|
`}
|
||
|
|
<button class="header-btn theme-btn" aria-label="Toggle theme">
|
||
|
|
<svg viewBox="0 0 24 24" width="24" height="24" class="theme-icon-dark">
|
||
|
|
<path fill="currentColor" d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
|
||
|
|
</svg>
|
||
|
|
<svg viewBox="0 0 24 24" width="24" height="24" class="theme-icon-light">
|
||
|
|
<path fill="currentColor" d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`);
|
||
|
|
}
|
||
|
|
|
||
|
|
bindEvents() {
|
||
|
|
this.on(this, 'click', this.handleClick);
|
||
|
|
this.on(this, 'submit', this.handleSubmit);
|
||
|
|
|
||
|
|
const searchInput = this.$('.search-input');
|
||
|
|
if (searchInput) {
|
||
|
|
this.on(searchInput, 'keydown', this.handleSearchKeydown);
|
||
|
|
}
|
||
|
|
|
||
|
|
window.addEventListener('rantii:auth-change', () => this.render());
|
||
|
|
}
|
||
|
|
|
||
|
|
handleClick(e) {
|
||
|
|
const target = e.target.closest('button, a');
|
||
|
|
if (!target) return;
|
||
|
|
|
||
|
|
if (target.classList.contains('menu-toggle')) {
|
||
|
|
e.preventDefault();
|
||
|
|
this.emit('menu-toggle');
|
||
|
|
} else if (target.classList.contains('logo') || target.closest('.logo')) {
|
||
|
|
e.preventDefault();
|
||
|
|
this.getRouter()?.goHome();
|
||
|
|
} else if (target.classList.contains('search-btn')) {
|
||
|
|
this.performSearch();
|
||
|
|
} else if (target.classList.contains('notifications-btn')) {
|
||
|
|
this.getRouter()?.goToNotifications();
|
||
|
|
} else if (target.classList.contains('post-btn')) {
|
||
|
|
this.emit('create-post');
|
||
|
|
} else if (target.classList.contains('user-btn')) {
|
||
|
|
this.emit('user-menu');
|
||
|
|
} else if (target.classList.contains('login-btn')) {
|
||
|
|
this.getRouter()?.goToLogin();
|
||
|
|
} else if (target.classList.contains('theme-btn')) {
|
||
|
|
this.getTheme()?.toggleDarkLight();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
handleSearchKeydown(e) {
|
||
|
|
if (e.key === 'Enter') {
|
||
|
|
e.preventDefault();
|
||
|
|
this.performSearch();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
handleSubmit(e) {
|
||
|
|
e.preventDefault();
|
||
|
|
}
|
||
|
|
|
||
|
|
performSearch() {
|
||
|
|
const input = this.$('.search-input');
|
||
|
|
if (input && input.value.trim()) {
|
||
|
|
this.getRouter()?.goToSearch(input.value.trim());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
setNotificationCount(count) {
|
||
|
|
const badge = this.$('.notification-badge');
|
||
|
|
if (badge) {
|
||
|
|
badge.textContent = count > 99 ? '99+' : count;
|
||
|
|
badge.hidden = count === 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
clearSearch() {
|
||
|
|
const input = this.$('.search-input');
|
||
|
|
if (input) {
|
||
|
|
input.value = '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
customElements.define('app-header', AppHeader);
|
||
|
|
|
||
|
|
export { AppHeader };
|