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 = `
`
}
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)