import { verifyEvent, NostrEvent } from '@nostr/tools/pure' import { decodeNostrURI, EventPointer, neventEncode, npubEncode, ProfilePointer } from '@nostr/tools/nip19' import { normalizeURL } from '@nostr/tools/utils' import { NostrUser } from '@nostr/gadgets/metadata' import * as nip10 from '@nostr/tools/nip10' import { fetchNostrUser, getOutboxRelaysFor, pool } from './nostr.js' import { debounce, nostrLink, shorten, splitComma } from './utils.js' import { renderText } from './text.js' export default class NostrNote extends HTMLElement { static observedAttributes = ['ref', 'event', 'relays', 'template'] root: HTMLDivElement constructor() { super() this.attachShadow({ mode: 'open' }) const { shadowRoot } = this this.root = document.createElement('div') shadowRoot!.appendChild(this.root) } connectedCallback() { setTimeout(() => { let template = this.getAttribute('template') ? (document.getElementById(this.getAttribute('template')!) as HTMLTemplateElement) : null if (template) { this.root.appendChild(template.content.cloneNode(true)) } else { this.root.innerHTML = `
parent
` } this.set() }, 1) } *queryPart(name: string): Generator { let slotted = this.querySelectorAll(`[part="${name}"]`) for (let i = 0; i < slotted.length; i++) { yield slotted[i] as E } let templated = this.root.querySelectorAll(`[part="${name}"]`) for (let i = 0; i < templated.length; i++) { yield templated[i] as E } } attributeChangedCallback() { this.set() } set: () => void = debounce(async () => { var evt: NostrEvent | null = null // if the raw event was provided as json, use that let eventj = this.getAttribute('event') if (eventj) { evt = JSON.parse(eventj) if (evt) { if (!verifyEvent(evt)) { return } } } if (!evt) { // otherwise try to fetch from ref let ref = this.getAttribute('ref') if (ref) { let { type, data } = decodeNostrURI(ref) if (type !== 'nevent') { return } let d = data as EventPointer let relays = (d.relays || []).concat(splitComma(this.getAttribute('relays')).map(normalizeURL)) let filter = { ids: [d.id] } // try to fetch just from relay hints if (d.author && relays.length === 0) relays = await getOutboxRelaysFor(d.author) evt = await pool.get(relays, filter) // if it fails try to fetch from the author's outbox relays if (!evt && d.author) { relays = await getOutboxRelaysFor(d.author) evt = await pool.get(relays, filter) } } } if (!evt) { return } let npub = npubEncode(evt.pubkey) for (let el of this.queryPart('author-link')) { el.href = nostrLink(npub) el.target = '_blank' } for (let el of this.queryPart('author-npub')) { el.textContent = npub } var nu: NostrUser | null = null for (let el of this.queryPart('author-name')) { if (!nu) { nu = await fetchNostrUser(evt.pubkey, []) } el.textContent = nu.shortName } for (let el of this.queryPart('author-npub-short')) { el.textContent = shorten(npub) } let thread: { root: EventPointer | undefined reply: EventPointer | undefined mentions: EventPointer[] quotes: EventPointer[] profiles: ProfilePointer[] } | null = null for (let el of this.queryPart('parent-link')) { if (!thread) thread = nip10.parse(evt) if (thread.reply) { el.href = nostrLink(neventEncode(thread.reply)) el.target = '_blank' el.style.display = '' } else { el.remove() } } for (let el of this.queryPart('root-link')) { if (!thread) thread = nip10.parse(evt) if (thread.root) { el.href = nostrLink(neventEncode(thread.root)) el.target = '_blank' } else { el.remove() } } for (let el of this.queryPart('link')) { el.href = nostrLink( neventEncode({ id: evt.id, relays: Array.from(pool.seenOn.get(evt.id) || []) .slice(0, 3) .map(r => r.url), author: evt.pubkey, }), ) el.target = '_blank' } for (let el of this.queryPart('date')) { let date = new Date(evt.created_at * 1000) el.textContent = date.toLocaleString() if (el.tagName === 'TIME') { el.setAttribute('time', date.toISOString().substring(0, 23)) } } let contentWrapper: HTMLDivElement | null = null for (let el of this.queryPart('content')) { if (!contentWrapper) { contentWrapper = document.createElement('div') el.appendChild(contentWrapper) } renderText(contentWrapper, evt.content) } }, 200) } window.customElements.define('nostr-note', NostrNote)