import {Event, nip19} from 'nostr-tools'; import {Children, elem, elemArticle, parseTextContent} from './utils/dom'; import {dateTime, formatTime} from './utils/time'; import {/*validatePow,*/ sortByCreatedAt} from './events'; import {getViewElem, getViewOptions, setViewElem} from './view'; import {config} from './settings'; import {getReactions, getReactionContents} from './reactions'; import {openWriteInput} from './write'; // import {linkPreview} from './media'; import {parseJSON} from './media'; import {getMetadata} from './profiles'; import {EventWithNip19, replyList} from './notes'; import {isFollowing} from './contacts'; setInterval(() => { document.querySelectorAll('time[datetime]').forEach((timeElem: HTMLTimeElement) => { timeElem.textContent = formatTime(new Date(timeElem.dateTime)); }); }, 10000); export const createTextNote = ( evt: EventWithNip19, relay: string, ) => { const {img, name, userName} = getMetadata(evt.pubkey); const replies = replyList.filter(({replyTo}) => replyTo === evt.id) .sort(sortByCreatedAt) .reverse(); // const isLongContent = evt.content.trimRight().length > 280; // const content = isLongContent ? evt.content.slice(0, 280) : evt.content; const reactions = getReactions(evt.id); const didReact = reactions.length && !!reactions.find(reaction => reaction.pubkey === config.pubkey); const [content, {firstLink}] = parseTextContent(evt.content); const time = new Date(evt.created_at * 1000); const buttons = elem('div', {className: 'buttons'}, [ elem('button', {name: 'reply', type: 'button'}, [ elem('img', {height: 24, width: 24, src: '/assets/comment.svg'}) ]), elem('button', {name: 'star', type: 'button'}, [ elem('img', { alt: didReact ? '✭' : '✩', // ♥ height: 24, width: 24, src: `/assets/${didReact ? 'star-fill' : 'star'}.svg`, title: getReactionContents(evt.id).join(' '), }), elem('small', {data: {reactions: ''}}, reactions.length || ''), ]), ]); if (localStorage.getItem('reply_to') === evt.id) { openWriteInput(buttons, evt.id); } const replyFeed: Array = replies[0] ? replies.map(e => setViewElem(e.id, createTextNote(e, relay))) : []; return elemArticle([ elem('a', {className: 'mbox-img', href: `/${evt.nip19.npub}`, tabIndex: -1}, img), elem('div', {className: 'mbox-body'}, [ elem('header', { className: 'mbox-header', title: `User: ${userName}\n${time}\n\nUser pubkey: ${evt.pubkey}\n\nRelay: ${relay}\n\nEvent-id: ${evt.id} ${evt.tags.length ? `\nTags ${JSON.stringify(evt.tags)}\n` : ''} ${evt.content}` }, [ elem('a', { className: `mbox-username${name ? ' mbox-kind0-name' : ''}`, data: {profile: evt.pubkey}, href: `/${evt.nip19.npub}`, }, name || userName), ' ', elem('a', {href: `/${evt.nip19.note}`}, elem('time', {dateTime: time.toISOString()}, formatTime(time))), ]), elem('div', {className: 'mbox-content'/* data: isLongContent ? {append: evt.content.slice(280)} : null*/}, content /*[ ...content, (firstLink && validatePow(evt)) ? linkPreview(firstLink, evt.id, relay) : null, ]*/), buttons, ]), ...(replies[0] ? [elem('div', {className: 'mbox-replies'}, replyFeed)] : []), ], { className: replies.length ? 'mbox-has-replies' : '', data: {kind: evt.kind, id: evt.id, pubkey: evt.pubkey, relay} }); }; type EventWithContent = Omit & { content: Children } export const renderUpdateContact = ( evt: EventWithContent, relay: string, ) => { const {img, name, userName} = getMetadata(evt.pubkey); const time = new Date(evt.created_at * 1000); return elemArticle([ elem('div', {className: 'mbox-img'}, img), elem('div', {className: 'mbox-body'}, [ elem('header', {className: 'mbox-header'}, [ elem('span', { className: `mbox-username${name ? ' mbox-kind0-name' : ''}`, data: {profile: evt.pubkey}, }, name || userName), ' ', elem('span', {data: {contacts: evt.id}, title: time.toISOString()}, evt.content), ]), ]), ], { className: 'mbox-updated-contact', data: {kind: evt.kind, id: evt.id, pubkey: evt.pubkey, relay} } ); }; export const renderRecommendServer = (evt: Event, relay: string) => { const {img, userName} = getMetadata(evt.pubkey); const time = new Date(evt.created_at * 1000); const body = elem('div', {className: 'mbox-body', title: dateTime.format(time)}, [ elem('header', {className: 'mbox-header'}, [ elem('small', {}, [ elem('strong', {}, userName) ]), ]), ` recommends server: ${evt.content}`, ]); return elemArticle([ elem('div', {className: 'mbox-img'}, [img]), body ], { className: 'mbox-recommend-server', data: {kind: evt.kind, id: evt.id, pubkey: evt.pubkey} }); }; export const renderEventDetails = (evt: Event, relay: string) => { const {img, name, userName} = getMetadata(evt.pubkey); const npub = nip19.npubEncode(evt.pubkey); let content = (![1, 7].includes(evt.kind) && evt.content !== '') ? parseJSON(evt.content) : (evt.content || ''); switch (typeof content) { case 'object': content = JSON.stringify(content, null, 2); break; default: content = `${content}`; } const body = elem('div', {className: 'mbox-body'}, [ elem('header', {className: 'mbox-header'}, [ elem('a', { className: `mbox-username${name ? ' mbox-kind0-name' : ''}`, data: {profile: evt.pubkey}, href: `/${npub}`, }, name || userName), ]), elem('dl', {}, [ elem('dt', {}, 'npub'), elem('dd', {}, npub), elem('dt', {}, 'created at'), elem('dd', {}, dateTime.format(evt.created_at * 1000)), elem('dt', {}, 'relay'), elem('dd', {}, relay), ]), elem('h2', {}, 'Event'), elem('dl', {}, [ elem('dt', {}, 'id'), elem('dd', {}, evt.id), elem('dt', {}, 'kind'), elem('dd', {}, evt.kind), elem('dt', {}, 'pubkey'), elem('dd', {}, evt.pubkey), elem('dt', {}, 'tags count'), elem('dd', {}, evt.tags.length), elem('dt', {}, 'tags'), elem('dd', {}, JSON.stringify(evt.tags)), elem('dt', {}, 'content'), elem('dd', {}, elem('pre', {}, content as string)), ]), ]); return elemArticle([ elem('a', {className: 'mbox-img', href: `/${npub}`, tabIndex: -1}, img), body, ], { className: 'mbox-plain-event', data: {kind: evt.kind, id: evt.id, pubkey: evt.pubkey} }); }; export const createContact = (pubkey: string) => { const {about: aboutContent, img, name, userName} = getMetadata(pubkey); const npub = nip19.npubEncode(pubkey); const view = getViewOptions(); if (view.type !== 'contacts') { return null; } const isMe = config.pubkey === pubkey; const isCurrentUser = view.id === pubkey; const hasContact = isFollowing(pubkey); const followStatus = elem('small'); const followBtn = elem('button', { className: hasContact ? 'secondary' : 'primary', ...(isMe && {disabled: true}), name: 'follow', data: {id: pubkey} }, hasContact ? (isMe ? 'following' : 'unfollow') : 'follow'); const about = elem('div', {className: 'mbox-content'}, aboutContent); setViewElem(`about-${pubkey}`, about); setViewElem(`followStatus-${pubkey}`, followStatus); setViewElem(`followBtn-${pubkey}`, followBtn); return elemArticle([ elem('a', {className: 'mbox-img', href: `/${npub}`, tabIndex: -1}, img), elem('div', {className: 'mbox-body'}, [ elem('header', {className: 'mbox-header'}, [ elem('a', { className: `mbox-username${name ? ' mbox-kind0-name' : ''}`, data: {profile: pubkey}, href: `/${npub}`, }, name || userName), (isMe || isCurrentUser) ? elem('small', {}, isMe ? '(your user)' : '(current user)') : null, ]), about, ]), elem('div', {className: 'mbox-cta'}, [followStatus, followBtn]), ], { className: 'mbox-contact', data: {pubkey}, }); };