- Add sidebar component to enhance site navigation and improve user experience - Implement window size constraints (min 1366x768) to ensure proper display across devices
304 lines
8.9 KiB
TypeScript
304 lines
8.9 KiB
TypeScript
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<typeof setTimeout>;
|
|
|
|
@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`
|
|
<header>
|
|
<div class="nav-buttons">
|
|
<button
|
|
class=${classMap({ disabled: !this.canGoBack })}
|
|
@click=${this._handleGoBack}
|
|
aria-label="Go back"
|
|
>
|
|
<iconify-icon icon="material-symbols:arrow-back"></iconify-icon>
|
|
</button>
|
|
<button
|
|
class=${classMap({ disabled: !this.canGoForward })}
|
|
@click=${this._handleGoForward}
|
|
aria-label="Go forward"
|
|
>
|
|
<iconify-icon icon="material-symbols:arrow-forward"></iconify-icon>
|
|
</button>
|
|
<button @click=${this._handleGoHome} aria-label="Home">
|
|
<iconify-icon icon="material-symbols:home"></iconify-icon>
|
|
</button>
|
|
</div>
|
|
<div class="search-container">
|
|
<arx-input
|
|
type="text"
|
|
.value=${this.searchQuery}
|
|
placeholder=${this.url}
|
|
@keyup=${this._handleSearch}
|
|
@focus=${this._handleFocus}
|
|
@change=${this._handleInput}
|
|
></arx-input>
|
|
${when(
|
|
this.showSuggestions,
|
|
() => html`
|
|
<div class="suggestions">
|
|
${map(this.suggestions, (suggestion: NDKEvent) =>
|
|
keyed(
|
|
suggestion.id,
|
|
html`
|
|
<arx-header-suggestion
|
|
.event=${suggestion}
|
|
.allEvents=${this.events}
|
|
@click=${() => this._handleSuggestionClick(suggestion)}
|
|
></arx-header-suggestion>
|
|
`,
|
|
),
|
|
)}
|
|
</div>
|
|
`,
|
|
)}
|
|
</div>
|
|
<div class="nav-buttons">
|
|
<button @click=${this._goToWallet}>
|
|
<iconify-icon icon="material-symbols:wallet"></iconify-icon>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
`;
|
|
}
|
|
|
|
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}`;
|
|
}
|
|
}
|
|
}
|