import { ndk } from '@/ndk'; import type { ArxInputChangeEvent } from '@components/General/Input'; import type { NDKEvent } from '@nostr-dev-kit/ndk'; import * as nip19 from '@nostr/tools/nip19'; import { LitElement, css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { keyed } from 'lit/directives/keyed.js'; import { map } from 'lit/directives/map.js'; import { when } from 'lit/directives/when.js'; import '@components/General/Input'; import '@components/HeaderSugestion'; @customElement('arx-header') export class Header extends LitElement { @property({ type: String }) override title = 'Eve'; @property({ type: String }) url = 'eve://home'; @property({ type: Boolean }) canGoBack = false; @property({ type: Boolean }) canGoForward = false; private searchQuery = ''; private _debounceTimeout?: ReturnType; @state() private showSuggestions = false; @state() private events: NDKEvent[] = []; @state() private suggestions: NDKEvent[] = []; static override styles = css` :host { display: block; z-index: 999999; } header { background: var(--color-primary); backdrop-filter: blur(10px); height: var(--font-2xl, 3rem); font-size: var(--font-md, 1rem); transition: all 0.3s ease; display: flex; align-items: space-between; padding: 0 var(--space-md, 1rem); } .nav-buttons { display: flex; gap: var(--space-xs, 0.5rem); padding: 0 var(--space-xs, 0.5rem); } button { text-decoration: none; color: var(--color-primary-content); background: oklch(from var(--color-primary-content) l c h / 0.1); border: var(--border) solid oklch(from var(--color-primary-content) l c h / 0.2); padding: var(--space-xs, 0.5rem); border-radius: 50%; font-size: var(--font-md, 1rem); backdrop-filter: blur(10px); display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; &:hover { background: oklch(from var(--color-primary-content) l c h / 0.2); transform: translateY(-2px); box-shadow: calc(var(--depth) * 2px) calc(var(--depth) * 2px) calc(var(--depth) * 4px) oklch(from var(--color-base-content) l c h / 0.15); } &:active { transform: translateY(1px); } &.disabled { opacity: 0.5; pointer-events: none; } } .search-container { flex: 1; position: relative; } arx-input { transform: translateY(3px); } .suggestions { position: absolute; top: 100%; left: 0; right: 0; background: oklch(from var(--color-base-100) l c h / 0.9); backdrop-filter: blur(10px); border: var(--border) solid var(--color-base-200); border-top: none; border-radius: 0 0 var(--radius-field) var(--radius-field); box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px) calc(var(--depth) * 8px) oklch(from var(--color-base-content) l c h / 0.2); z-index: 9999; max-height: 200px; overflow-y: auto; overflow-x: hidden; } `; override firstUpdated() { ndk .fetchEvents({ limit: 50_000, }) .then((events) => { this.events = [...events]; }); } override render() { return html`
${when( this.showSuggestions, () => html`
${map(this.suggestions, (suggestion: NDKEvent) => keyed( suggestion.id, html` this._handleSuggestionClick(suggestion)} > `, ), )}
`, )}
`; } private _goToWallet() { window.location.hash = 'wallet'; } private _handleFocus() { this.showSuggestions = true; } private _handleInput(e: ArxInputChangeEvent) { this.searchQuery = e.detail.value; if (this._debounceTimeout) { clearTimeout(this._debounceTimeout); } if (!this.searchQuery.trim()) { this.suggestions = []; this.showSuggestions = false; return; } this._debounceTimeout = setTimeout(() => { const query = this.searchQuery.toLowerCase(); const isNoteSearch = query.startsWith('note1'); const isEventSearch = query.startsWith('nevent1'); const isPubkeySearch = query.startsWith('npub1'); this.suggestions = this.events .filter((event: NDKEvent) => { if (event.kind === 11 || event.kind === 1111) return false; // hide old forum events if (event.kind === 30890 || event.kind === 30891) return false; // ignore old-in-dev events if (event.kind === 60890) return false; // don't include the actual forum if (event.kind === 60891) { const categoryId = event.tags.find((tag) => tag[0] === 'd')?.[1] || ''; const forum = this.events.find( (e) => e.kind === 60890 && e.tags.some((tag) => tag[0] === 'forum' && tag[1] === `60891:${categoryId}`), ); if (!forum) return false; // ignore orphan forum categories } if (event.id.includes(query)) return true; if (isNoteSearch) { const noteId = nip19.noteEncode(event.id); return noteId.includes(query); } if (isEventSearch) { const eventId = event.encode(); return eventId.includes(query); } if (isPubkeySearch && event.kind === 0) { const pubkey = nip19.npubEncode(event.pubkey); return pubkey.includes(query); } if (event.content.toLowerCase().includes(query)) return true; return event.tags.some((tag) => tag.length > 1 && tag[1].toLowerCase().includes(query)); }) .slice(0, 20); this.showSuggestions = this.suggestions.length > 0; }, 50); this.showSuggestions = true; } private _handleSearch(e: KeyboardEvent) { if (e.key !== 'Enter') return; this.showSuggestions = false; if (this.searchQuery.startsWith('npub1')) { try { const { type } = nip19.decode(this.searchQuery); if (type === 'npub') { window.location.hash = `profile/${this.searchQuery}`; return; } } catch (e) {} } const hash = this.searchQuery.replace('eve://', '#'); window.location.hash = hash; } private _handleGoBack() { this.dispatchEvent(new CustomEvent('go-back')); } private _handleGoForward() { this.dispatchEvent(new CustomEvent('go-forward')); } private _handleGoHome() { window.location.hash = '#'; } private _handleSuggestionClick(suggestion: NDKEvent) { window.location.hash = '#'; this.showSuggestions = false; console.log(suggestion); switch (suggestion.kind) { case 0: window.location.hash = `profile/${suggestion.pubkey}`; break; case 60890: case 60891: window.location.hash = 'arbor'; break; case 892: window.location.hash = `arbor/topics/${suggestion.id}`; break; case 893: { const threadId = suggestion.tags.find((tag) => tag[0] === 'E')?.[1]!; window.location.hash = `arbor/topics/${threadId}`; break; } default: window.location.hash = `event/${suggestion.id}`; } } }