feat: render history_loaded in web UI with earlier messages divider
- store: handle history_loaded event — prepend gossipped messages, dedup by mid, sort by ts, record cutoff timestamp per room - MessagePane: show "earlier messages" divider between history and live messages based on the cutoff timestamp - types: add history_loaded, room_created, create_room to IpcMsgType; add messages field to IpcMessage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -158,3 +158,5 @@ details summary { color: var(--muted); font-size: 12px; cursor: pointer; }
|
|||||||
.file-entry-dir { cursor: pointer; }
|
.file-entry-dir { cursor: pointer; }
|
||||||
.file-entry-dir:hover { background: rgba(255,255,255,0.04); }
|
.file-entry-dir:hover { background: rgba(255,255,255,0.04); }
|
||||||
.file-entry-icon { font-size: 12px; flex-shrink: 0; }
|
.file-entry-icon { font-size: 12px; flex-shrink: 0; }
|
||||||
|
.history-divider { display: flex; align-items: center; gap: 8px; margin: 10px 0 6px; color: var(--muted); font-size: 11px; }
|
||||||
|
.history-divider::before, .history-divider::after { content: ''; flex: 1; height: 1px; background: var(--border); }
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { useWaste } from '../store'
|
import { useWaste } from '../store'
|
||||||
|
|
||||||
export function MessagePane() {
|
export function MessagePane() {
|
||||||
const { messages, activeRoom, activeNetworkId, localPeer, connectedPeers, send } = useWaste()
|
const { messages, historyCutoff, activeRoom, activeNetworkId, localPeer, connectedPeers, send } = useWaste()
|
||||||
const [draft, setDraft] = useState('')
|
const [draft, setDraft] = useState('')
|
||||||
const bottomRef = useRef<HTMLDivElement>(null)
|
const bottomRef = useRef<HTMLDivElement>(null)
|
||||||
const roomMessages = messages[activeRoom] ?? []
|
const roomMessages = messages[activeRoom] ?? []
|
||||||
|
const cutoff = historyCutoff[activeRoom] ?? 0
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
|
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||||
@@ -46,11 +47,19 @@ export function MessagePane() {
|
|||||||
const mine = msg.from === localPeer?.id
|
const mine = msg.from === localPeer?.id
|
||||||
const alias = aliasFor(msg.from)
|
const alias = aliasFor(msg.from)
|
||||||
const time = new Date(msg.ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })
|
const time = new Date(msg.ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })
|
||||||
|
const showDivider = cutoff > 0 && i > 0 && roomMessages[i - 1].ts <= cutoff && msg.ts > cutoff
|
||||||
return (
|
return (
|
||||||
<div key={msg.mid ?? i} className={`message ${mine ? 'mine' : ''}`}>
|
<div key={msg.mid ?? i}>
|
||||||
<span className="message-ts">{time}</span>
|
{showDivider && (
|
||||||
<span className="message-alias">{alias}</span>
|
<div className="history-divider">
|
||||||
<span className="message-text">{msg.text}</span>
|
<span>earlier messages</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={`message ${mine ? 'mine' : ''}`}>
|
||||||
|
<span className="message-ts">{time}</span>
|
||||||
|
<span className="message-alias">{alias}</span>
|
||||||
|
<span className="message-text">{msg.text}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ interface WasteState {
|
|||||||
|
|
||||||
// chat — keyed by room
|
// chat — keyed by room
|
||||||
messages: Record<string, ChatMessage[]>
|
messages: Record<string, ChatMessage[]>
|
||||||
|
// rooms for which we have received history: room → ts of last history message
|
||||||
|
historyCutoff: Record<string, number>
|
||||||
activeRoom: string
|
activeRoom: string
|
||||||
// user-created rooms, keyed by networkId
|
// user-created rooms, keyed by networkId
|
||||||
customRooms: Record<string, string[]>
|
customRooms: Record<string, string[]>
|
||||||
@@ -84,6 +86,7 @@ export const useWaste = create<WasteState>((set, get) => ({
|
|||||||
activeNetworkId: null,
|
activeNetworkId: null,
|
||||||
connectedPeers: [],
|
connectedPeers: [],
|
||||||
messages: {},
|
messages: {},
|
||||||
|
historyCutoff: {},
|
||||||
activeRoom: 'general',
|
activeRoom: 'general',
|
||||||
customRooms: {},
|
customRooms: {},
|
||||||
fileLists: {},
|
fileLists: {},
|
||||||
@@ -352,6 +355,24 @@ export const useWaste = create<WasteState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'history_loaded': {
|
||||||
|
const room = msg.room
|
||||||
|
const incoming = (msg.messages ?? []) as ChatMessage[]
|
||||||
|
if (!room || incoming.length === 0) break
|
||||||
|
set(s => {
|
||||||
|
const existing = s.messages[room] ?? []
|
||||||
|
const existingMids = new Set(existing.map(m => m.mid).filter(Boolean))
|
||||||
|
const fresh = incoming.filter(m => !m.mid || !existingMids.has(m.mid))
|
||||||
|
if (fresh.length === 0) return s
|
||||||
|
const merged = [...fresh, ...existing].sort((a, b) => a.ts - b.ts)
|
||||||
|
const cutoff = fresh[fresh.length - 1]?.ts ?? 0
|
||||||
|
return {
|
||||||
|
messages: { ...s.messages, [room]: merged },
|
||||||
|
historyCutoff: { ...s.historyCutoff, [room]: cutoff },
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ export type IpcMsgType =
|
|||||||
| 'shares_list'
|
| 'shares_list'
|
||||||
| 'peer_status'
|
| 'peer_status'
|
||||||
| 'error'
|
| 'error'
|
||||||
|
| 'history_loaded'
|
||||||
|
| 'room_created'
|
||||||
|
| 'create_room'
|
||||||
|
|
||||||
export interface IpcMessage {
|
export interface IpcMessage {
|
||||||
type: IpcMsgType
|
type: IpcMsgType
|
||||||
@@ -124,4 +127,6 @@ export interface IpcMessage {
|
|||||||
conn_state?: PeerConnState
|
conn_state?: PeerConnState
|
||||||
candidate_type?: CandidateType
|
candidate_type?: CandidateType
|
||||||
remote_address?: string
|
remote_address?: string
|
||||||
|
// history_loaded
|
||||||
|
messages?: ChatMessage[]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user