You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
4.4 KiB
122 lines
4.4 KiB
import { Controller } from '@hotwired/stimulus'; |
|
|
|
const LOADING_HTML = `<div class="nostr-preview__loading text-center my-2"><span class="nostr-preview__spinner" role="status" aria-label="Loading"></span><span class="nostr-preview__loading-text ms-2">Loading preview…</span></div>`; |
|
const UNAVAILABLE_HTML = `<div class="alert alert-warning my-2" role="status">Preview unavailable.</div>`; |
|
|
|
/** |
|
* @param {HTMLElement} el |
|
* @param {string} type |
|
* @param {string} decodedStr |
|
* @returns {boolean} |
|
*/ |
|
function isPreviewForSameArticleOnPage(el, type, decodedStr) { |
|
const root = el.closest('[data-nostr-page-article-coordinate]'); |
|
if (!root) { |
|
return false; |
|
} |
|
const pageCoord = root.getAttribute('data-nostr-page-article-coordinate') || ''; |
|
const pageEid = (root.getAttribute('data-nostr-page-article-event-id') || '').toLowerCase(); |
|
const pagePubHex = (root.getAttribute('data-nostr-page-article-pubkey-hex') || '').toLowerCase(); |
|
const pageNpub = root.getAttribute('data-nostr-page-article-npub') || ''; |
|
if (!pageCoord) { |
|
return false; |
|
} |
|
let d; |
|
try { |
|
d = JSON.parse(decodedStr); |
|
} catch { |
|
return false; |
|
} |
|
if (type === 'naddr' && d && d.pubkey != null) { |
|
const identRaw = d.identifier != null ? d.identifier : (d.specifier != null ? d.specifier : null); |
|
if (identRaw == null) { |
|
return false; |
|
} |
|
const k = d.kind != null ? parseInt(String(d.kind), 10) : 30023; |
|
const ident = String(identRaw); |
|
let pk = String(d.pubkey); |
|
if (/^[0-9a-fA-F]{64}$/.test(pk)) { |
|
pk = pk.toLowerCase(); |
|
} else if (pk.startsWith('npub1') && pageNpub) { |
|
if (pk !== pageNpub) { |
|
return false; |
|
} |
|
pk = pagePubHex; |
|
} else { |
|
return false; |
|
} |
|
if (!pk || pk.length !== 64) { |
|
return false; |
|
} |
|
const candidate = `${k}:${pk}:${ident}`; |
|
|
|
return candidate === pageCoord; |
|
} |
|
if (type === 'nevent' && d && d.id && pageEid) { |
|
return String(d.id).toLowerCase() === pageEid; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
export default class extends Controller { |
|
static values = { |
|
identifier: String, |
|
type: String, |
|
decoded: String, |
|
fullMatch: String, |
|
}; |
|
|
|
static targets = ['container']; |
|
|
|
connect() { |
|
if (this.typeValue === 'naddr' || this.typeValue === 'nevent') { |
|
if (isPreviewForSameArticleOnPage(this.element, this.typeValue, this.decodedValue)) { |
|
this.element.setAttribute('hidden', ''); |
|
this.element.setAttribute('data-nostr-preview-suppressed', 'same-page-article'); |
|
return; |
|
} |
|
} |
|
this.fetchPreview(); |
|
} |
|
|
|
async fetchPreview() { |
|
if (!this.hasContainerTarget) { |
|
return; |
|
} |
|
this.containerTarget.innerHTML = LOADING_HTML; |
|
try { |
|
if (this.typeValue === 'url' && this.fullMatchValue) { |
|
const res = await fetch('/og-preview/', { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ url: this.fullMatchValue }), |
|
}); |
|
if (!res.ok) { |
|
throw new Error(`HTTP ${res.status}`); |
|
} |
|
this.containerTarget.innerHTML = await res.text(); |
|
return; |
|
} |
|
const res = await fetch('/preview/', { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ |
|
identifier: this.identifierValue, |
|
type: this.typeValue, |
|
decoded: this.decodedValue, |
|
}), |
|
}); |
|
if (!res.ok) { |
|
throw new Error(`HTTP ${res.status}`); |
|
} |
|
this.containerTarget.innerHTML = await res.text(); |
|
} catch (e) { |
|
// NetworkError / offline: avoid console.error noise; one inline fallback per block |
|
console.debug('nostr_preview: fetch failed', e); |
|
this.containerTarget.innerHTML = this.typeValue === 'url' && this.fullMatchValue |
|
? `<div class="alert alert-warning my-2" role="status">Unable to load link preview for ${this.fullMatchValue}.</div>` |
|
: UNAVAILABLE_HTML; |
|
} |
|
} |
|
}
|
|
|