mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-12 19:50:04 +05:00
Added Chat History browser
This commit is contained in:
parent
33e7e867d7
commit
7d7adb8b83
@ -4,6 +4,7 @@ const peopleUtil = require('people/people_util');
|
||||
const people = require('people/people');
|
||||
const chatState = require('chat/chat_state');
|
||||
const chatEmoji = require('chat/chat_emoji');
|
||||
const HistoryBrowserModal = require('people/people_history');
|
||||
|
||||
const {
|
||||
get64Num,
|
||||
@ -235,32 +236,45 @@ const ChatRoomHeader = () => {
|
||||
]),
|
||||
m('.chat-header-actions', [
|
||||
isDistant
|
||||
? m(
|
||||
'button.red',
|
||||
{
|
||||
title: 'Leave Distant Chat',
|
||||
onclick: () => {
|
||||
if (confirm('Are you sure you want to leave this distant chat conversation?')) {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/closeDistantChatConnexion',
|
||||
{
|
||||
pid: lobbyHexId,
|
||||
},
|
||||
(data, success) => {
|
||||
if (success) {
|
||||
ChatLobbyModel.stopStatusPolling();
|
||||
ChatHubState.selectedRoom = null;
|
||||
ChatHubState.selectedRoomId = null;
|
||||
ChatHubState.selectedRoomType = null;
|
||||
m.route.set('/chat');
|
||||
}
|
||||
}
|
||||
);
|
||||
? [
|
||||
m(
|
||||
'button.blue',
|
||||
{
|
||||
title: 'View distant chat history',
|
||||
style: 'margin-right: 0.75rem;',
|
||||
onclick: () => {
|
||||
ChatHubState.showHistoryModal = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
[m('i.fas.fa-sign-out-alt'), ' Leave Chat']
|
||||
)
|
||||
[m('i.fas.fa-history'), ' History']
|
||||
),
|
||||
m(
|
||||
'button.red',
|
||||
{
|
||||
title: 'Leave Distant Chat',
|
||||
onclick: () => {
|
||||
if (confirm('Are you sure you want to leave this distant chat conversation?')) {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/closeDistantChatConnexion',
|
||||
{
|
||||
pid: lobbyHexId,
|
||||
},
|
||||
(data, success) => {
|
||||
if (success) {
|
||||
ChatLobbyModel.stopStatusPolling();
|
||||
ChatHubState.selectedRoom = null;
|
||||
ChatHubState.selectedRoomId = null;
|
||||
ChatHubState.selectedRoomType = null;
|
||||
m.route.set('/chat');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
[m('i.fas.fa-sign-out-alt'), ' Leave Chat']
|
||||
)
|
||||
]
|
||||
: [
|
||||
m(
|
||||
'button',
|
||||
@ -274,6 +288,17 @@ const ChatRoomHeader = () => {
|
||||
},
|
||||
[m('i.fas.fa-user-plus'), ' Invite']
|
||||
),
|
||||
m(
|
||||
'button.blue',
|
||||
{
|
||||
title: 'View chat room history',
|
||||
style: 'margin-right: 0.75rem;',
|
||||
onclick: () => {
|
||||
ChatHubState.showHistoryModal = true;
|
||||
}
|
||||
},
|
||||
[m('i.fas.fa-history'), ' History']
|
||||
),
|
||||
m(
|
||||
'button.red',
|
||||
{
|
||||
@ -557,6 +582,7 @@ const ChatConversationView = () => {
|
||||
])
|
||||
])
|
||||
]),
|
||||
m(HistoryBrowserModal, { isRoom: true }),
|
||||
]),
|
||||
m('.chat-hub-rightbar', [
|
||||
m('.rightbar-title', 'Participants'),
|
||||
|
||||
@ -532,6 +532,38 @@ const ChatLobbyModel = {
|
||||
}
|
||||
);
|
||||
},
|
||||
loadAllHistoryForRoom(lobbyId, callback) {
|
||||
ChatHubState.isHistoryLoading = true;
|
||||
ChatHubState.fullHistoryMessages = [];
|
||||
m.redraw();
|
||||
|
||||
const chatType = this.currentLobby && this.currentLobby.chatType;
|
||||
const isDistant = chatType === 2;
|
||||
|
||||
const chatPeerId = {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: isDistant ? 2 : 3,
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: isDistant ? (lobbyId || '') : '00000000000000000000000000000000',
|
||||
lobby_id: { xstr64: isDistant ? '0' : (lobbyId || '0') },
|
||||
};
|
||||
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsHistory/getMessages',
|
||||
{
|
||||
chatPeerId: chatPeerId,
|
||||
loadCount: 0,
|
||||
},
|
||||
(data, success) => {
|
||||
let msgs = (success && data && data.msgs) ? data.msgs : [];
|
||||
msgs.sort((a, b) => (a.sendTime || a.recvTime) - (b.sendTime || b.recvTime));
|
||||
ChatHubState.fullHistoryMessages = msgs;
|
||||
ChatHubState.isHistoryLoading = false;
|
||||
m.redraw();
|
||||
if (callback) callback();
|
||||
}
|
||||
);
|
||||
},
|
||||
setupAction: (lobbyId, nick) => { },
|
||||
setIdentity(lobbyId, nick) {
|
||||
rs.rsJsonApiRequest(
|
||||
@ -772,6 +804,10 @@ const ChatHubState = {
|
||||
showInviteModal: false,
|
||||
friendsList: [],
|
||||
selectedFriendsToInvite: new Set(),
|
||||
showHistoryModal: false,
|
||||
historySearchQuery: '',
|
||||
fullHistoryMessages: [],
|
||||
isHistoryLoading: false,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@ -8,10 +8,12 @@ const {
|
||||
initializeDistantChat,
|
||||
sendDistantChatMessage,
|
||||
stopStatusPolling,
|
||||
loadAllHistoryForSelectedPeer,
|
||||
} = require('people/people_state');
|
||||
const { renderChatMessage } = require('chat/chat_state');
|
||||
const chatEmoji = require('chat/chat_emoji');
|
||||
const peopleUtil = require('people/people_util');
|
||||
const HistoryBrowserModal = require('people/people_history');
|
||||
|
||||
// Mirroring C++ Distant Chat packet size limit (200KB)
|
||||
function formatChatImage(file, callback) {
|
||||
@ -98,6 +100,14 @@ const ChatTab = () => {
|
||||
|
||||
const canTalk = State.distantChatStatus && State.distantChatStatus.status === 2;
|
||||
|
||||
// Filter history by search query for history modal
|
||||
const query = (State.historySearchQuery || '').toLowerCase();
|
||||
const filteredHistory = (State.fullHistoryMessages || []).filter((msg) => {
|
||||
if (!query) return true;
|
||||
const text = (msg.msg || msg.message || '').toLowerCase();
|
||||
return text.includes(query);
|
||||
});
|
||||
|
||||
return m('.network-chat-view', [
|
||||
m('.chat-identity-select-container', {
|
||||
style: 'padding: 0.5rem 1rem; background-color: #ffffff; border-bottom: 1px solid #cbd5e1; display: flex; align-items: center; justify-content: space-between; font-size: 0.85rem;',
|
||||
@ -113,7 +123,7 @@ const ChatTab = () => {
|
||||
title: getStatusTooltip(State.distantChatStatus ? State.distantChatStatus.status : 0),
|
||||
}),
|
||||
]),
|
||||
m('.chat-actions', { style: 'display: flex; align-items: center; gap: 1rem;' }, [
|
||||
m('.chat-actions', { style: 'display: flex; align-items: center; gap: 0.75rem;' }, [
|
||||
m('.select-own-profile', { style: 'display: flex; align-items: center; gap: 0.5rem;' }, [
|
||||
m('span', { style: 'color: #64748b;' }, 'Chatting as:'),
|
||||
(() => {
|
||||
@ -137,6 +147,18 @@ const ChatTab = () => {
|
||||
]);
|
||||
})(),
|
||||
]),
|
||||
m('button.blue.history-btn', {
|
||||
style: 'padding: 0.25rem 0.75rem; border-radius: 0.25rem; font-size: 0.85rem; display: flex; align-items: center; gap: 0.35rem; border: none; cursor: pointer; background-color: #3b82f6; color: #ffffff; font-weight: 600;',
|
||||
title: 'View all past chat history with this contact',
|
||||
onclick: () => {
|
||||
State.showHistoryModal = true;
|
||||
State.historySearchQuery = '';
|
||||
loadAllHistoryForSelectedPeer();
|
||||
},
|
||||
}, [
|
||||
m('i.fas.fa-history', { style: 'color: #ffffff;' }),
|
||||
'History',
|
||||
]),
|
||||
m('button.red.leave-btn', {
|
||||
style: 'padding: 0.25rem 0.75rem; border-radius: 0.25rem; font-size: 0.85rem; display: flex; align-items: center; gap: 0.25rem; border: none; cursor: pointer; background-color: #ef4444; color: #ffffff;',
|
||||
onclick: () => {
|
||||
@ -304,6 +326,9 @@ const ChatTab = () => {
|
||||
},
|
||||
}, [m('i.fas.fa-paper-plane'), ' Send']),
|
||||
]),
|
||||
|
||||
// Chat History Browser Modal
|
||||
m(HistoryBrowserModal),
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
126
webui-src/app/people/people_history.js
Normal file
126
webui-src/app/people/people_history.js
Normal file
@ -0,0 +1,126 @@
|
||||
const m = require('mithril');
|
||||
const rs = require('rswebui');
|
||||
const peopleState = require('people/people_state');
|
||||
const chatState = require('chat/chat_state');
|
||||
|
||||
const HistoryBrowserModal = () => {
|
||||
return {
|
||||
oninit: (vnode) => {
|
||||
const isRoom = vnode.attrs && vnode.attrs.isRoom;
|
||||
if (isRoom) {
|
||||
chatState.ChatHubState.historySearchQuery = '';
|
||||
const lobbyId = chatState.ChatLobbyModel.currentLobby ? rs.idToHex(chatState.ChatLobbyModel.currentLobby.lobby_id) : null;
|
||||
if (lobbyId) {
|
||||
chatState.ChatLobbyModel.loadAllHistoryForRoom(lobbyId);
|
||||
}
|
||||
} else {
|
||||
peopleState.State.historySearchQuery = '';
|
||||
peopleState.loadAllHistoryForSelectedPeer();
|
||||
}
|
||||
},
|
||||
view: (vnode) => {
|
||||
const isRoom = vnode.attrs && vnode.attrs.isRoom;
|
||||
const stateObj = isRoom ? chatState.ChatHubState : peopleState.State;
|
||||
|
||||
if (!stateObj.showHistoryModal) return null;
|
||||
|
||||
let name = 'Chat History';
|
||||
if (isRoom) {
|
||||
const lobby = chatState.ChatLobbyModel.currentLobby;
|
||||
name = lobby ? lobby.lobby_name : 'Chat Room';
|
||||
} else {
|
||||
const details = peopleState.State.selectedId ? peopleState.State.gxsIdToDetailsMap[peopleState.State.selectedId] : null;
|
||||
name = details ? (details.mNickname || details.mGroupName || 'Contact') : 'Contact';
|
||||
}
|
||||
|
||||
const query = (stateObj.historySearchQuery || '').toLowerCase();
|
||||
const filteredHistory = (stateObj.fullHistoryMessages || []).filter((msg) => {
|
||||
if (!query) return true;
|
||||
const text = (msg.msg || msg.message || '').toLowerCase();
|
||||
return text.includes(query);
|
||||
});
|
||||
|
||||
return m('.history-modal-overlay', {
|
||||
style: 'position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(15, 23, 42, 0.4); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 2000;',
|
||||
onclick: (e) => {
|
||||
if (e.target === e.currentTarget) stateObj.showHistoryModal = false;
|
||||
}
|
||||
}, [
|
||||
m('.history-modal', {
|
||||
style: 'background: #ffffff; border-radius: 0.5rem; width: 780px; max-width: 92%; height: 85vh; max-height: 85vh; display: flex; flex-direction: column; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); overflow: hidden;'
|
||||
}, [
|
||||
// Header
|
||||
m('.history-modal-header', {
|
||||
style: 'padding: 1rem 1.25rem; background: #f8fafc; border-bottom: 1px solid #e2e8f0; display: flex; align-items: center; justify-content: space-between;'
|
||||
}, [
|
||||
m('.history-title', { style: 'display: flex; align-items: center; gap: 0.5rem;' }, [
|
||||
m('i.fas.fa-history', { style: 'color: #3b82f6; font-size: 1.2rem;' }),
|
||||
m('h3', { style: 'margin: 0; font-size: 1.1rem; font-weight: 700; color: #1e293b;' }, `Chat History Browser — ${name}`),
|
||||
]),
|
||||
m('button.close-btn', {
|
||||
style: 'background: transparent; border: none; font-size: 1.25rem; color: #64748b; cursor: pointer; padding: 0.25rem; border-radius: 0.25rem;',
|
||||
title: 'Close history browser',
|
||||
onclick: () => (stateObj.showHistoryModal = false),
|
||||
}, m('i.fas.fa-times')),
|
||||
]),
|
||||
|
||||
// Toolbar
|
||||
m('.history-toolbar', {
|
||||
style: 'padding: 0.75rem 1.25rem; background: #ffffff; border-bottom: 1px solid #f1f5f9; display: flex; align-items: center; justify-content: space-between; gap: 1rem;'
|
||||
}, [
|
||||
m('.search-input-box', { style: 'position: relative; flex: 1;' }, [
|
||||
m('i.fas.fa-search', { style: 'position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%); color: #94a3b8; font-size: 0.85rem;' }),
|
||||
m('input[type=text][placeholder=Search past messages or keywords...]', {
|
||||
style: 'width: 100%; padding: 0.4rem 0.75rem 0.4rem 2.2rem; border-radius: 0.375rem; border: 1px solid #cbd5e1; outline: none; font-size: 0.85rem;',
|
||||
value: stateObj.historySearchQuery || '',
|
||||
oninput: (e) => (stateObj.historySearchQuery = e.target.value),
|
||||
}),
|
||||
]),
|
||||
m('span.history-count', { style: 'font-size: 0.85rem; color: #64748b; font-weight: 600;' },
|
||||
`${filteredHistory.length} messages`
|
||||
),
|
||||
]),
|
||||
|
||||
// Message Body
|
||||
m('.history-message-list', {
|
||||
style: 'flex: 1; overflow-y: auto; padding: 1rem 1.25rem; display: flex; flex-direction: column; gap: 0.75rem; background: #f8fafc;'
|
||||
}, [
|
||||
stateObj.isHistoryLoading
|
||||
? m('.loading-spinner', { style: 'text-align: center; padding: 3rem; color: #64748b;' }, [
|
||||
m('i.fas.fa-spinner.fa-spin', { style: 'font-size: 2rem; margin-bottom: 0.75rem; color: #3b82f6;' }),
|
||||
m('p', { style: 'font-weight: 600;' }, 'Fetching complete chat history from Retroshare database...'),
|
||||
])
|
||||
: filteredHistory.length === 0
|
||||
? m('.empty-history', { style: 'text-align: center; padding: 3rem; color: #64748b;' }, [
|
||||
m('i.far.fa-comments', { style: 'font-size: 2.5rem; color: #cbd5e1; margin-bottom: 0.75rem;' }),
|
||||
m('p', 'No past chat messages found matching your query.'),
|
||||
])
|
||||
: filteredHistory.map((msg) => {
|
||||
const isIncoming = msg.incoming;
|
||||
let senderName = msg.peerName || (isIncoming ? name : 'You');
|
||||
if (!isIncoming) {
|
||||
const ownId = isRoom ? (chatState.ChatLobbyModel.currentLobby ? chatState.ChatLobbyModel.currentLobby.gxs_id : '') : peopleState.State.selectedOwnGxsIdForChat;
|
||||
senderName = rs.userList.username(ownId) || 'You';
|
||||
}
|
||||
const timeStr = new Date((msg.sendTime || msg.recvTime || 0) * 1000).toLocaleString();
|
||||
|
||||
return m('.history-item', {
|
||||
style: 'background: #ffffff; border: 1px solid #e2e8f0; border-radius: 0.5rem; padding: 0.75rem 1rem; box-shadow: 0 1px 2px rgba(0,0,0,0.03);'
|
||||
}, [
|
||||
m('.history-item-header', { style: 'display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.4rem;' }, [
|
||||
m('span.sender', { style: `font-weight: 700; font-size: 0.85rem; color: ${isIncoming ? '#3b82f6' : '#10b981'};` }, senderName),
|
||||
m('span.time', { style: 'font-size: 0.75rem; color: #94a3b8;' }, timeStr),
|
||||
]),
|
||||
m('.history-item-body', { style: 'font-size: 0.9rem; color: #334155; word-break: break-word;' },
|
||||
chatState.renderChatMessage(msg.msg || msg.message || '')
|
||||
),
|
||||
]);
|
||||
})
|
||||
]),
|
||||
])
|
||||
]);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = HistoryBrowserModal;
|
||||
@ -22,6 +22,10 @@ const State = {
|
||||
statusPollInterval: null,
|
||||
chatDisconnected: false,
|
||||
activeMenu: null,
|
||||
showHistoryModal: false,
|
||||
historySearchQuery: '',
|
||||
fullHistoryMessages: [],
|
||||
isHistoryLoading: false,
|
||||
};
|
||||
|
||||
function fetchIdDetails(gxsId) {
|
||||
@ -308,7 +312,7 @@ function loadChatMessages() {
|
||||
|
||||
const chatPeerId = {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: 2, // DISTANT
|
||||
type: 2, // TYPE_PRIVATE_DISTANT
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: State.chatPid,
|
||||
lobby_id: { xstr64: '0' },
|
||||
@ -350,7 +354,7 @@ function sendDistantChatMessage() {
|
||||
|
||||
const cid = {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: 2, // DISTANT
|
||||
type: 2, // TYPE_PRIVATE_DISTANT
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: State.chatPid,
|
||||
lobby_id: { xstr64: '0' },
|
||||
@ -405,10 +409,10 @@ function preloadAllChatHistory() {
|
||||
const gxsId = typeof u === 'object' ? u.mGroupId : u;
|
||||
if (!gxsId) return;
|
||||
|
||||
// Check Distant Chat History (type: 2)
|
||||
// Check Distant Chat History (type: 2 - TYPE_PRIVATE_DISTANT)
|
||||
const distantPeerId = {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: 2, // DISTANT
|
||||
type: 2, // TYPE_PRIVATE_DISTANT
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: gxsId,
|
||||
lobby_id: { xstr64: '0' },
|
||||
@ -480,10 +484,89 @@ function preloadAllChatHistory() {
|
||||
});
|
||||
}
|
||||
|
||||
function loadAllHistoryForSelectedPeer(callback) {
|
||||
if (!State.selectedId) return;
|
||||
|
||||
State.isHistoryLoading = true;
|
||||
State.fullHistoryMessages = [];
|
||||
m.redraw();
|
||||
|
||||
const queries = [];
|
||||
|
||||
// Query 1: Distant Chat History by active chatPid (type: 2 - TYPE_PRIVATE_DISTANT)
|
||||
if (State.chatPid) {
|
||||
queries.push({
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: 2, // TYPE_PRIVATE_DISTANT
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: State.chatPid,
|
||||
lobby_id: { xstr64: '0' },
|
||||
});
|
||||
}
|
||||
|
||||
// Query 2: Distant Chat History by selectedId if different (type: 2 - TYPE_PRIVATE_DISTANT)
|
||||
if (State.selectedId && State.selectedId !== State.chatPid) {
|
||||
queries.push({
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: 2, // TYPE_PRIVATE_DISTANT
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: State.selectedId,
|
||||
lobby_id: { xstr64: '0' },
|
||||
});
|
||||
}
|
||||
|
||||
// Query 3: Private Chat History by PGP ID if available (type: 1 - TYPE_PRIVATE)
|
||||
const details = State.gxsIdToDetailsMap[State.selectedId];
|
||||
const pgpId = details ? details.mPgpId : null;
|
||||
if (pgpId && pgpId !== '0000000000000000') {
|
||||
queries.push({
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: 1, // TYPE_PRIVATE
|
||||
peer_id: pgpId,
|
||||
distant_chat_id: '00000000000000000000000000000000',
|
||||
lobby_id: { xstr64: '0' },
|
||||
});
|
||||
}
|
||||
|
||||
let accumulatedMsgs = [];
|
||||
let completed = 0;
|
||||
|
||||
queries.forEach((chatPeerId) => {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsHistory/getMessages',
|
||||
{
|
||||
chatPeerId: chatPeerId,
|
||||
loadCount: 0, // 0 = load all messages in C++
|
||||
},
|
||||
(msgData, success) => {
|
||||
if (success && msgData && msgData.msgs) {
|
||||
accumulatedMsgs = accumulatedMsgs.concat(msgData.msgs);
|
||||
}
|
||||
completed++;
|
||||
if (completed === queries.length) {
|
||||
const map = new Map();
|
||||
accumulatedMsgs.forEach((mItem) => {
|
||||
const text = mItem.msg || mItem.message || '';
|
||||
const key = `${mItem.sendTime || mItem.recvTime}_${text}`;
|
||||
if (!map.has(key)) map.set(key, mItem);
|
||||
});
|
||||
let uniqueMsgs = Array.from(map.values());
|
||||
uniqueMsgs.sort((a, b) => (a.sendTime || a.recvTime) - (b.sendTime || b.recvTime));
|
||||
State.fullHistoryMessages = uniqueMsgs;
|
||||
State.isHistoryLoading = false;
|
||||
m.redraw();
|
||||
if (callback) callback();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
State,
|
||||
isSystemMsg,
|
||||
preloadAllChatHistory,
|
||||
loadAllHistoryForSelectedPeer,
|
||||
fetchIdDetails,
|
||||
loadGxsIdentities,
|
||||
loadOwnGxsIds,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user