mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-14 11:05:47 +05:00
Fixed to not lose distant chat when you navigate to other pages
Added counter to show active distant chats
This commit is contained in:
parent
ec21174f22
commit
70f5fd9975
@ -9,9 +9,11 @@ const {
|
||||
loadOwnGxsIds,
|
||||
preloadAllChatHistory,
|
||||
syncFilter,
|
||||
startStatusPolling,
|
||||
stopStatusPolling,
|
||||
initializeDistantChat,
|
||||
} = require('people/people_state');
|
||||
|
||||
const PeopleSidebar = require('people/people_sidebar');
|
||||
const DetailsTab = require('people/people_details_tab');
|
||||
const ChatTab = require('people/people_chat_tab');
|
||||
@ -36,9 +38,41 @@ const PeopleLayout = () => {
|
||||
// Register for chatEvents to receive live incoming messages
|
||||
rs.events[15].notify = (chatMessage) => {
|
||||
const msgCid = chatMessage.chat_id;
|
||||
if (msgCid && msgCid.type === 2 && State.chatPid) {
|
||||
if (msgCid && msgCid.type === 2) {
|
||||
const msgPid = rs.idToHex(msgCid.distant_chat_id);
|
||||
if (msgPid === State.chatPid) {
|
||||
|
||||
// Find active session matching this distant chat PID
|
||||
let session = null;
|
||||
let targetGxsId = null;
|
||||
Object.keys(State.activeDistantChats || {}).forEach((id) => {
|
||||
if (State.activeDistantChats[id] && State.activeDistantChats[id].pid === msgPid) {
|
||||
session = State.activeDistantChats[id];
|
||||
targetGxsId = id;
|
||||
}
|
||||
});
|
||||
|
||||
if (session) {
|
||||
const isNearDuplicate = session.messages.some(
|
||||
(m) => (m.msg || m.message) === chatMessage.msg && Math.abs(m.sendTime - chatMessage.sendTime) < 5
|
||||
);
|
||||
if (!isNearDuplicate) {
|
||||
session.messages.push(chatMessage);
|
||||
session.messages.sort((a, b) => a.sendTime - b.sendTime);
|
||||
if (targetGxsId) {
|
||||
State.chatHistoryMap[targetGxsId] = {
|
||||
lastMsg: chatMessage.msg || chatMessage.message || '',
|
||||
lastTime: chatMessage.sendTime || Math.floor(Date.now() / 1000),
|
||||
};
|
||||
}
|
||||
m.redraw();
|
||||
if (State.selectedId === targetGxsId) {
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector('.chat-messages');
|
||||
if (element) element.scrollTop = element.scrollHeight;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
} else if (State.chatPid && msgPid === State.chatPid) {
|
||||
const isNearDuplicate = State.chatMessages.some(
|
||||
(m) => (m.msg || m.message) === chatMessage.msg && Math.abs(m.sendTime - chatMessage.sendTime) < 5
|
||||
);
|
||||
@ -54,6 +88,10 @@ const PeopleLayout = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (State.chatPid && !State.chatDisconnected) {
|
||||
startStatusPolling();
|
||||
}
|
||||
},
|
||||
onremove: () => {
|
||||
if (rs.events[15]) {
|
||||
@ -62,6 +100,7 @@ const PeopleLayout = () => {
|
||||
stopStatusPolling();
|
||||
window.removeEventListener('click', dismissMenu);
|
||||
},
|
||||
|
||||
onupdate: (vnode) => {
|
||||
syncFilter(vnode.attrs.tab);
|
||||
},
|
||||
|
||||
@ -141,7 +141,7 @@ const ChatTab = () => {
|
||||
value: ownId,
|
||||
onchange: (e) => {
|
||||
State.selectedOwnGxsIdForChat = e.target.value;
|
||||
initializeDistantChat();
|
||||
initializeDistantChat(true);
|
||||
},
|
||||
}, State.ownGxsIds.map((id) => m('option', { value: id }, rs.userList.username(id)))),
|
||||
]);
|
||||
@ -170,6 +170,9 @@ const ChatTab = () => {
|
||||
},
|
||||
(data, success) => {
|
||||
if (success) {
|
||||
if (State.selectedId && State.activeDistantChats[State.selectedId]) {
|
||||
delete State.activeDistantChats[State.selectedId];
|
||||
}
|
||||
State.chatPid = null;
|
||||
State.chatMessages = [];
|
||||
State.distantChatStatus = null;
|
||||
@ -182,6 +185,7 @@ const ChatTab = () => {
|
||||
}
|
||||
},
|
||||
}, [
|
||||
|
||||
m('i.fas.fa-sign-out-alt'),
|
||||
'Leave Chat',
|
||||
]),
|
||||
|
||||
@ -35,6 +35,17 @@ const PeopleSidebar = () => {
|
||||
// 1. Determine list based on mainTab ('people' vs 'chats')
|
||||
let displayItems = [];
|
||||
|
||||
// 0. Compute active chats count (conversations with real message history)
|
||||
const allUserGroupIds = new Set((rs.userList.users || []).map((u) => u.mGroupId));
|
||||
Object.keys(State.chatHistoryMap || {}).forEach((id) => allUserGroupIds.add(id));
|
||||
let activeChatsCount = 0;
|
||||
allUserGroupIds.forEach((gxsId) => {
|
||||
const hist = State.chatHistoryMap && State.chatHistoryMap[gxsId];
|
||||
if (hist && hist.lastMsg && !isSystemMsg(hist.lastMsg)) {
|
||||
activeChatsCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (State.mainTab === 'people') {
|
||||
let baseList = [];
|
||||
if (State.activeFilter === 'own') {
|
||||
@ -57,10 +68,7 @@ const PeopleSidebar = () => {
|
||||
});
|
||||
} else {
|
||||
// Chats Tab: ONLY contacts and identities that have real chat history (ignoring system tunnel status logs)
|
||||
const userGroupIds = new Set((rs.userList.users || []).map((u) => u.mGroupId));
|
||||
Object.keys(State.chatHistoryMap || {}).forEach((id) => userGroupIds.add(id));
|
||||
|
||||
displayItems = Array.from(userGroupIds)
|
||||
displayItems = Array.from(allUserGroupIds)
|
||||
.map((gxsId) => {
|
||||
const entry = rs.userList.userMap[gxsId];
|
||||
const name = entry && entry.name ? entry.name : (rs.userList.username(gxsId) || 'Unknown');
|
||||
@ -127,10 +135,15 @@ const PeopleSidebar = () => {
|
||||
m.redraw();
|
||||
},
|
||||
},
|
||||
[m('i.fas.fa-comments'), ' Chats']
|
||||
[
|
||||
m('i.fas.fa-comments'),
|
||||
' Chats',
|
||||
activeChatsCount > 0 && m('span.segment-badge', activeChatsCount),
|
||||
]
|
||||
),
|
||||
]),
|
||||
|
||||
|
||||
// 3. Sub-Filter Row (People Tab)
|
||||
State.mainTab === 'people' &&
|
||||
m('.sub-filter-row', [
|
||||
|
||||
@ -21,6 +21,7 @@ const State = {
|
||||
distantChatStatus: null,
|
||||
statusPollInterval: null,
|
||||
chatDisconnected: false,
|
||||
activeDistantChats: {}, // gxsId -> { pid, status, messages, inputMsg, disconnected }
|
||||
activeMenu: null,
|
||||
showHistoryModal: false,
|
||||
historySearchQuery: '',
|
||||
@ -28,6 +29,21 @@ const State = {
|
||||
isHistoryLoading: false,
|
||||
};
|
||||
|
||||
function getDistantChatSession(gxsId) {
|
||||
if (!gxsId) return null;
|
||||
if (!State.activeDistantChats[gxsId]) {
|
||||
State.activeDistantChats[gxsId] = {
|
||||
pid: null,
|
||||
status: null,
|
||||
messages: [],
|
||||
inputMsg: '',
|
||||
disconnected: false,
|
||||
};
|
||||
}
|
||||
return State.activeDistantChats[gxsId];
|
||||
}
|
||||
|
||||
|
||||
function fetchIdDetails(gxsId) {
|
||||
if (!gxsId) return;
|
||||
if (State.gxsIdToDetailsMap[gxsId] === undefined) {
|
||||
@ -175,11 +191,6 @@ function syncFilter(tab) {
|
||||
|
||||
if (State.activeFilter !== newFilter) {
|
||||
State.activeFilter = newFilter;
|
||||
State.selectedId = null;
|
||||
State.chatPid = null;
|
||||
State.chatMessages = [];
|
||||
State.chatInputMsg = '';
|
||||
State.activeTab = 'details';
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,6 +214,8 @@ function getStatusTooltip(status) {
|
||||
|
||||
function pollDistantChatStatus() {
|
||||
if (!State.chatPid) return;
|
||||
const session = State.selectedId ? getDistantChatSession(State.selectedId) : null;
|
||||
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/getDistantChatStatus',
|
||||
{
|
||||
@ -211,6 +224,7 @@ function pollDistantChatStatus() {
|
||||
(detail, success) => {
|
||||
if (success && detail.retval) {
|
||||
State.distantChatStatus = detail.info;
|
||||
if (session) session.status = detail.info;
|
||||
|
||||
if (detail.info.status === 2) {
|
||||
const text = 'Tunnel is secured. You can talk!';
|
||||
@ -258,7 +272,6 @@ function stopStatusPolling() {
|
||||
clearInterval(State.statusPollInterval);
|
||||
State.statusPollInterval = null;
|
||||
}
|
||||
State.distantChatStatus = null;
|
||||
}
|
||||
|
||||
function isSystemMsg(msgText) {
|
||||
@ -273,11 +286,28 @@ function isSystemMsg(msgText) {
|
||||
);
|
||||
}
|
||||
|
||||
function initializeDistantChat() {
|
||||
function initializeDistantChat(force = false) {
|
||||
if (!State.selectedId || !State.selectedOwnGxsIdForChat) return;
|
||||
|
||||
State.chatPid = null;
|
||||
State.chatMessages = [
|
||||
const session = getDistantChatSession(State.selectedId);
|
||||
|
||||
// If chat session is already established/initiating for this peer and not forced/disconnected:
|
||||
if (!force && session.pid && !session.disconnected) {
|
||||
State.chatPid = session.pid;
|
||||
State.chatMessages = session.messages;
|
||||
State.distantChatStatus = session.status;
|
||||
State.chatDisconnected = session.disconnected;
|
||||
|
||||
loadChatMessages();
|
||||
pollDistantChatStatus();
|
||||
startStatusPolling();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, start a new tunnel for this peer
|
||||
session.pid = null;
|
||||
session.status = null;
|
||||
session.messages = [
|
||||
{
|
||||
incoming: true,
|
||||
isSystem: true,
|
||||
@ -285,6 +315,11 @@ function initializeDistantChat() {
|
||||
sendTime: Math.floor(Date.now() / 1000),
|
||||
}
|
||||
];
|
||||
session.disconnected = false;
|
||||
|
||||
State.chatPid = null;
|
||||
State.chatMessages = session.messages;
|
||||
State.distantChatStatus = null;
|
||||
State.chatDisconnected = false;
|
||||
m.redraw();
|
||||
|
||||
@ -297,7 +332,9 @@ function initializeDistantChat() {
|
||||
},
|
||||
(res) => {
|
||||
if (res && res.pid) {
|
||||
State.chatPid = rs.idToHex(res.pid);
|
||||
const hexPid = rs.idToHex(res.pid);
|
||||
session.pid = hexPid;
|
||||
State.chatPid = hexPid;
|
||||
State.distantChatStatus = null;
|
||||
loadChatMessages();
|
||||
pollDistantChatStatus();
|
||||
@ -307,6 +344,7 @@ function initializeDistantChat() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function loadChatMessages() {
|
||||
if (!State.chatPid) return;
|
||||
|
||||
@ -564,6 +602,7 @@ function loadAllHistoryForSelectedPeer(callback) {
|
||||
|
||||
module.exports = {
|
||||
State,
|
||||
getDistantChatSession,
|
||||
isSystemMsg,
|
||||
preloadAllChatHistory,
|
||||
loadAllHistoryForSelectedPeer,
|
||||
@ -586,3 +625,4 @@ module.exports = {
|
||||
loadChatMessages,
|
||||
sendDistantChatMessage,
|
||||
};
|
||||
|
||||
|
||||
@ -58,3 +58,140 @@ img.avatar {
|
||||
color: green;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.people-sidebar-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.75rem 1rem 0.5rem 1rem;
|
||||
gap: 0.75rem;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
background-color: #ffffff;
|
||||
|
||||
.searchbar-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i.fa-search {
|
||||
position: absolute;
|
||||
left: 0.85rem;
|
||||
color: #94a3b8;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
input.searchbar-input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem 0.5rem 2.25rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
background-color: #f8fafc;
|
||||
color: #1e293b;
|
||||
outline: none;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
border-color: #3b82f6;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.segmented-control {
|
||||
display: flex;
|
||||
background-color: #f1f5f9;
|
||||
padding: 3px;
|
||||
border-radius: 0.5rem;
|
||||
gap: 4px;
|
||||
|
||||
button.segment-tab {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #ffffff;
|
||||
color: #0f172a;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.segment-badge {
|
||||
background-color: #019dff;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.segment-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #cbd5e1;
|
||||
color: #334155;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
min-width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
padding: 0 0.35rem;
|
||||
border-radius: 9999px;
|
||||
line-height: 1;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sub-filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
min-height: 32px;
|
||||
|
||||
select.filter-select {
|
||||
padding: 0.35rem 0.6rem;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
background-color: #ffffff;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-add-id {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 0.375rem;
|
||||
background-color: #3b82f6;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -689,6 +689,28 @@ h1{font-size:3rem}h2{font-size:2.25rem}h3{font-size:1.875rem}h4{font-size:1.5rem
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.people-sidebar-header .segmented-control button.segment-tab .segment-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #cbd5e1;
|
||||
color: #334155;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
min-width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
padding: 0 0.35rem;
|
||||
border-radius: 9999px;
|
||||
line-height: 1;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.people-sidebar-header .segmented-control button.segment-tab.active .segment-badge {
|
||||
background-color: #019dff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
|
||||
/* Sub-header Filter Row */
|
||||
.people-sidebar-header .sub-filter-row {
|
||||
display: flex;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user