@ -4,15 +4,15 @@ import {elem} from './utils/dom';
import { bounce } from './utils/time' ;
import { isWssUrl } from './utils/url' ;
import { closeSettingsView , config , toggleSettingsView } from './settings' ;
import { subGlobalFeed , subEventID , subNote , subProfile , subPubkeys , subOwnContacts } from './subscriptions'
import { subGlobalFeed , subEventID , subNote , subProfile , subPubkeys , subOwnContacts , subContactList } from './subscriptions'
import { getReplyTo , hasEventTag , isEvent , isMention , sortByCreatedAt , sortEventCreatedAt } from './events' ;
import { clearView , getViewContent , getViewElem , getViewOptions , setViewElem , view } from './view' ;
import { handleReaction , handleUpvote } from './reactions' ;
import { closePublishView , openWriteInput , togglePublishView } from './write' ;
import { handleMetadata , renderProfile } from './profiles' ;
import { followContact , getContactUpdateMessage , getContacts , resetContactList, setContactList , updateContactList , updateFollowBtn } from './contacts' ;
import { followContact , getContactUpdateMessage , getContacts , getOwnContacts, refreshFollowing , resetContactList, setContactList , updateContactList , updateFollowBtn } from './contacts' ;
import { EventWithNip19 , EventWithNip19AndReplyTo , textNoteList , replyList } from './notes' ;
import { create TextNote, renderEventDetails , renderRecommendServer , renderUpdateContact } from './ui' ;
import { create Contact, create TextNote, renderEventDetails , renderRecommendServer , renderUpdateContact } from './ui' ;
// curl -H 'accept: application/nostr+json' https://relay.nostr.ch/
@ -38,6 +38,18 @@ const renderNote = (
setViewElem ( evt . id , article ) ;
} ;
const renderContact = ( pubkey : string ) = > {
if ( getViewElem ( ` contact- ${ pubkey } ` ) ) { // contact already in view
updateFollowBtn ( pubkey ) ;
return ;
}
const contact = createContact ( pubkey ) ;
if ( contact ) {
getViewContent ( ) . append ( contact ) ;
setViewElem ( ` contact- ${ pubkey } ` , contact ) ;
}
} ;
const hasEnoughPOW = (
[ tag , , commitment ] : string [ ] ,
eventId : string
@ -64,12 +76,13 @@ const renderFeed = bounce(() => {
]
. sort ( sortByCreatedAt )
. reverse ( )
. forEach ( renderNote ) ; // render in-reply-to
. forEach ( renderNote ) ;
renderProfile ( view . id ) ;
refreshFollowing ( view . id ) ;
break ;
case 'home' :
const ids = get Contacts( ) ;
const ids = get Own Contacts( ) ;
[
. . . textNoteList
. filter ( note = > ids . includes ( note . pubkey ) ) ,
@ -95,6 +108,10 @@ const renderFeed = bounce(() => {
. reverse ( )
. forEach ( renderNote ) ;
break ;
case 'contacts' :
getContacts ( view . id )
. forEach ( renderContact ) ;
break ;
}
} , 17 ) ; // (16.666 rounded, an arbitrary value to limit updates to max 60x per s)
@ -167,30 +184,35 @@ const handleContactList = (evt: Event, relay: string) => {
// TODO: if newer and view.type === 'home' rerenderFeed()
setContactList ( evt ) ;
const view = getViewOptions ( ) ;
if ( getViewElem ( evt . id ) ) {
return ;
}
if (
getViewElem ( evt . id )
|| view . type !== 'profile'
|| view . id !== evt . pubkey
view . type === 'contacts'
&& [ view . id , config . pubkey ] . includes ( evt . pubkey ) // render if contact-list is from current users or current view
) {
renderFeed ( ) ;
return ;
}
// use find instead of sort?
const closestTextNotes = textNoteList . sort ( sortEventCreatedAt ( evt . created_at ) ) ;
const closestNote = getViewElem ( closestTextNotes [ 0 ] . id ) ;
if ( ! closestNote ) {
// no close note, try later
setTimeout ( ( ) = > handleContactList ( evt , relay ) , 1500 ) ;
return ;
} ;
const [ addedContacts , removedContacts ] = updateContactList ( evt ) ;
const content = getContactUpdateMessage ( addedContacts , removedContacts ) ;
if ( ! content . length ) {
// P same as before, maybe only evt.content or 'a' tags changed?
return ;
if ( view . type === 'profile' && view . id === evt . pubkey ) {
// use find instead of sort?
const closestTextNotes = textNoteList . sort ( sortEventCreatedAt ( evt . created_at ) ) ;
const closestNote = getViewElem ( closestTextNotes [ 0 ] . id ) ;
if ( ! closestNote ) {
// no close note, try later
setTimeout ( ( ) = > handleContactList ( evt , relay ) , 1500 ) ;
return ;
} ;
const [ addedContacts , removedContacts ] = updateContactList ( evt ) ;
const content = getContactUpdateMessage ( addedContacts , removedContacts ) ;
if ( ! content . length ) {
// P same as before, maybe only evt.content or 'a' tags changed?
return ;
}
const art = renderUpdateContact ( { . . . evt , content } , relay ) ;
closestNote . after ( art ) ;
setViewElem ( evt . id , art ) ;
}
const art = renderUpdateContact ( { . . . evt , content } , relay ) ;
closestNote . after ( art ) ;
setViewElem ( evt . id , art ) ;
} ;
const handleRecommendServer = ( evt : Event , relay : string ) = > {
@ -243,10 +265,9 @@ const onEvent = (evt: Event, relay: string) => {
// subscribe and change view
const route = ( path : string ) = > {
const contactList = getContacts ( ) ;
if ( path === '/' ) {
const contactList = getOwnContacts ( ) ;
if ( contactList . length ) {
const { pubkey } = config ;
subPubkeys ( contactList , onEvent ) ;
view ( ` / ` , { type : 'home' } ) ;
} else {
@ -278,18 +299,25 @@ const route = (path: string) => {
console . warn ( ` type ${ type } not yet supported ` ) ;
}
renderFeed ( ) ;
} else if ( path . length === 73 && path . match ( /^\/contacts\/npub[0-9a-z]+$/ ) ) {
const contactNpub = path . slice ( 10 ) ;
const { type : contactType , data : contactPubkey } = nip19 . decode ( contactNpub ) ;
if ( contactType === 'npub' ) {
subContactList ( contactPubkey , onEvent ) ;
view ( path , { type : 'contacts' , id : contactPubkey } ) ;
}
} else if ( path . length === 65 ) {
const eventID = path . slice ( 1 ) ;
subEventID ( eventID , onEventDetails ) ;
view ( path , { type : 'event' , id : eventID } ) ;
} else {
console . warn ( 'no support for ' , path )
console . warn ( 'no support for ' , path ) ;
}
} ;
// onload
subOwnContacts ( onEvent ) ;
route ( location . pathname ) ;
subOwnContacts ( onEvent ) ; // subscribe after route as routing unsubscribes current subs
// only push a new entry if there is no history onload
if ( ! history . length ) {
@ -317,6 +345,7 @@ const handleLink = (a: HTMLAnchorElement, e: MouseEvent) => {
|| href . startsWith ( '/feed' )
|| href . startsWith ( '/note' )
|| href . startsWith ( '/npub' )
|| href . startsWith ( '/contacts/npub' )
|| ( href . startsWith ( '/' ) && href . length === 65 )
) {
route ( href ) ;