Compare commits

..
9 Commits
9 changed files with 56 additions and 32 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
"@fortawesome/free-regular-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/react-fontawesome": "0.2.2",
"@noble/hashes": "^1.8.0",
"@noble/hashes": "1.8.0",
"axios": "0.27.2",
"bch-dex-lib": "2.2.0",
"bch-message-lib": "2.2.1",
+1 -1
View File
@@ -7,7 +7,7 @@
"@fortawesome/free-regular-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/react-fontawesome": "0.2.2",
"@noble/hashes": "^1.8.0",
"@noble/hashes": "1.8.0",
"axios": "0.27.2",
"bch-dex-lib": "2.2.0",
"bch-message-lib": "2.2.1",
@@ -38,6 +38,7 @@ export default function ChannelItem (props) {
>
{chInfo?.name &&
<div className='d-flex align-items-center'>
{chInfo.picture && <img src={chInfo.picture} alt={chInfo.name} style={{ width: '30px', marginRight: '5px' }} />}
<span
className='fw-medium'
style={{
@@ -26,9 +26,13 @@ function ChatHeader (props) {
return (
<div className='p-3 d-flex justify-content-between align-items-center' style={{ backgroundColor: '#ffffff' }}>
{chInfo &&
<div className='flex-grow-1'>
<h5 className='mb-1 text-dark'>{chInfo?.name || getShortName(selectedChannel)}</h5>
<small className='text-muted'>{chInfo?.about || ''}</small>
<div className='d-flex justify-content-center align-items-center'>
{chInfo.picture && <img src={chInfo.picture} alt={chInfo.name} style={{ width: '50px', marginRight: '5px' }} />}
<div className='flex-grow-1'>
<h5 className='mb-1 text-dark'>{chInfo?.name || getShortName(selectedChannel)}</h5>
<small className='text-muted'>{chInfo?.about || ''}</small>
</div>
</div>}
{!chInfo &&
+21 -6
View File
@@ -6,6 +6,7 @@
import React, { useCallback, useEffect, useState, useRef } from 'react'
import { Container, Row, Col } from 'react-bootstrap'
import { RelayPool } from 'nostr'
// Local libraries
import ChatSidebar from './chat-sidebar'
import ChatMain from './chat-main'
@@ -18,6 +19,7 @@ function NostrChat (props) {
const [loadedMessages, setLoadedMessages] = useState(false)
const [profiles, setProfiles] = useState({})
const [channelsData, setChannelsData] = useState({})
const [channelsLoaded, setChannelsLoaded] = useState(false)
const [channels] = useState(config.chatsId)
const [selectedChannel, setSelectedChannel] = useState(config.chatsId[0])
@@ -26,14 +28,17 @@ function NostrChat (props) {
// Handle read messages
const onMsgRead = useCallback(async (msg) => {
try {
// console.log('onMsgRead() msg: ', msg)
// Update messages list
setMessages(current => {
const exist = current.find(val => val.id === msg.id)
// ignore existing messages
const exist = current.find(val => val.id === msg.id)
if (exist) return current
const newMsgs = [...current]
newMsgs.push(msg)
// Sort messages by timestamp
newMsgs.sort((a, b) => b.created_at - a.created_at)
return newMsgs.reverse()
@@ -45,9 +50,15 @@ function NostrChat (props) {
if (existProfile) {
return
}
// Fech profile.
let profile = await appData.nostrQueries.getProfile(pubKey)
if (!profile) profile = { name: pubKey }
console.log(`Trying to get ${pubKey} profile.`)
const defaultProfile = { name: pubKey } // default profile
profilesRef.current[pubKey] = defaultProfile // update ref , to prevent fetch this profile again.
// Fetch profile
const nostrProfile = await appData.nostrQueries.getProfile(pubKey)
const profile = nostrProfile || defaultProfile
// Update profiles state
setProfiles(currentProfiles => {
const newProfiles = { ...currentProfiles }
@@ -62,7 +73,8 @@ function NostrChat (props) {
// Handle nostr pool
useEffect(() => {
if (!selectedChannel) return
// fetch messages when channel selected and channel metadata are loaded
if (!selectedChannel || !channelsLoaded) return
const relays = nostrQueries.relays
if (relays.length === 0) {
@@ -75,6 +87,7 @@ function NostrChat (props) {
pool.on('open', relay => {
relay.subscribe('REQ', { limit: 10, kinds: [42], '#e': [selectedChannel] })
})
pool.on('eose', relay => {
setLoadedMessages(true)
})
@@ -83,12 +96,13 @@ function NostrChat (props) {
console.log('post retrieved from ', relay.url, ev.content)
onMsgRead(ev)
})
return () => {
// Close pool on component unmount or selected channel changes
console.log('Close existing pool')
pool.close()
}
}, [onMsgRead, selectedChannel, nostrQueries])
}, [onMsgRead, selectedChannel, nostrQueries, channelsLoaded])
// Load channels data
useEffect(() => {
@@ -112,6 +126,7 @@ function NostrChat (props) {
newChs[ch] = channelData
return newChs
})
setChannelsLoaded(true)
}
}
@@ -3,16 +3,16 @@
*/
// Global npm libraries
import React, { useEffect, useRef, useState } from 'react'
import React, { useEffect, useState } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUser } from '@fortawesome/free-solid-svg-icons'
import { Spinner } from 'react-bootstrap'
import NostrFormat from '../../app-body/nostr/nostr-format'
function MessageItem (props) {
const { message, profiles } = props
const [profile, setProfile] = useState(null)
const msgRef = useRef(null)
// Format timestamp
const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString([], {
@@ -26,19 +26,10 @@ function MessageItem (props) {
if (!profile && profiles[message.pubkey]) {
setProfile(profiles[message.pubkey])
}
}, [profiles, message])
useEffect(() => {
if (msgRef.current && message.latest) {
msgRef.current.scrollIntoView({
behavior: 'smooth',
block: 'end'
})
}
}, [msgRef.current, message])
}, [profiles, message, profile])
return (
<div className='mb-3 d-flex align-items-start' ref={msgRef}>
<div className='mb-3 d-flex align-items-start'>
{/* Message Content */}
<div className='flex-grow-1'>
<div
@@ -80,13 +71,14 @@ function MessageItem (props) {
}}
/>}
</div>
{!profile && <span className='fw-bold text-dark me-2'>{message.pubkey}</span>}
{!profile && <span className='fw-bold text-dark me-2'>{message.pubkey}<Spinner animation='border' size='sm' /></span>}
{profile && profile.name && <span className='fw-bold text-dark me-2'>{profile.name}</span>}
<small className='text-muted'>{formatTime(message.created_at * 1000)}</small>
</div>
<div style={{ lineHeight: '1.4', wordBreak: 'break-all' }}>
{message.content}
<NostrFormat content={message.content} />
</div>
</div>
</div>
@@ -3,7 +3,7 @@
*/
// Global npm libraries
import React, { useEffect, useState, useCallback } from 'react'
import React, { useEffect, useState, useCallback, useRef } from 'react'
// Local libraries
import MessageItem from './message-item'
@@ -13,12 +13,11 @@ import { Spinner } from 'react-bootstrap'
function MessageList (props) {
const { messages, loadedMessages } = props
const [groupedMessages, setGroupedMessages] = useState([])
const msgContainerRef = useRef()
// Group messages by date
const groupMessagesByDate = useCallback((messages) => {
const grouped = {}
messages.forEach((message, i) => {
if (i === messages.length - 1) message.latest = true
const date = new Date(message.created_at * 1000).toDateString()
if (!grouped[date]) {
grouped[date] = []
@@ -33,6 +32,18 @@ function MessageList (props) {
const grouped = groupMessagesByDate(messages)
setGroupedMessages(grouped)
// Scroll to end of the message container.
// Wait few seconds before load render.
setTimeout(() => {
if (msgContainerRef.current) {
msgContainerRef.current.scrollTo({
top: msgContainerRef.current.scrollHeight - msgContainerRef.current.clientHeight,
behavior: 'smooth'
})
msgContainerRef.current = null // scroll one time
}
}, 1000)
}, [messages, groupMessagesByDate])
return (
@@ -66,7 +77,7 @@ function MessageList (props) {
)}
{loadedMessages && messages && messages.length > 0 && (
<div className='p-3' style={{ overflowY: 'auto', maxHeight: '50vh' }}>
<div ref={msgContainerRef} className='p-3' style={{ overflowY: 'auto', maxHeight: '50vh' }}>
{Object.entries(groupedMessages).map(([date, dateMessages]) => (
<div key={date}>
<DateSeparator date={date} />
+1 -1
View File
@@ -24,7 +24,7 @@ const config = {
'wss://nos.lol',
'wss://relay.damus.io'
],
chatsId: [
chatsId: [
'32b0ebc01c984008977fbe476e064946990df75525331b24b37bbba5e89f4039'
]
+1
View File
@@ -54,6 +54,7 @@ export default class NostrQueries {
relay.close()
})
pool.on('error', (relay) => {
console.log(`Error fetching ${pubHex} profile. relay connection error :${relay.url} `)
relay.close()
resolve(false)
})