mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-14 11:05:47 +05:00
Fixed Network active chats
- Fixed. Chat previews - Implemented unread chat counts
This commit is contained in:
parent
c7011ff280
commit
209adb6564
33
webui-src/app/chat/chat_preview.js
Normal file
33
webui-src/app/chat/chat_preview.js
Normal file
@ -0,0 +1,33 @@
|
||||
function chatPreviewText(rawText) {
|
||||
if (!rawText) return '';
|
||||
|
||||
const source = String(rawText);
|
||||
if (!/[<&]/.test(source)) return source.trim();
|
||||
|
||||
const hasImage = /<img\b/i.test(source) || /<img\b/i.test(source);
|
||||
const decodeEntities = (text) => {
|
||||
const decoder = document.createElement('textarea');
|
||||
decoder.innerHTML = text;
|
||||
return decoder.value;
|
||||
};
|
||||
const stripMarkup = (text) => text
|
||||
.replace(/<(style|script|head)\b[^>]*>[\s\S]*?<\/\1>/gi, ' ')
|
||||
.replace(/<br\s*\/?>/gi, ' ')
|
||||
.replace(/<\/p\s*>|<\/div\s*>/gi, ' ')
|
||||
.replace(/<[^>]+>/g, ' ');
|
||||
|
||||
// Some peers send literal HTML while others send the same payload with its
|
||||
// tags entity-encoded. Decode and strip a second time for the latter form.
|
||||
let text = decodeEntities(stripMarkup(source));
|
||||
if (/<[^>]+>/.test(text)) text = decodeEntities(stripMarkup(text));
|
||||
text = text
|
||||
.replace(/\u00a0/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
if (text) return text;
|
||||
if (hasImage) return 'Photo';
|
||||
return 'Message';
|
||||
}
|
||||
|
||||
module.exports = chatPreviewText;
|
||||
@ -10,6 +10,8 @@ const {
|
||||
startDirectChat,
|
||||
getOnlineSslId,
|
||||
preloadNetworkChatHistory,
|
||||
loadDirectChatMessages,
|
||||
markDirectChatRead,
|
||||
} = require('network/network_state');
|
||||
const { OwnProfileCard, FriendsList } = require('network/network_friends_list');
|
||||
const DetailsTab = require('network/network_details_tab');
|
||||
@ -19,6 +21,8 @@ const NetworkGraph = require('network/network_graph');
|
||||
const NetworkLayout = () => {
|
||||
return {
|
||||
oninit: () => {
|
||||
// Keep the active-chat list current even when no conversation is open.
|
||||
loadDirectChatMessages();
|
||||
Data.refreshGpgDetails().then(() => {
|
||||
preloadNetworkChatHistory();
|
||||
m.redraw();
|
||||
@ -71,6 +75,7 @@ const NetworkLayout = () => {
|
||||
onclick: () => {
|
||||
State.activeTab = 'chat';
|
||||
State.mobilePane = 'detail';
|
||||
markDirectChatRead(State.selectedFriendGpgId);
|
||||
const sslId = getOnlineSslId(State.selectedFriendGpgId);
|
||||
if (sslId && !State.currentChatPeerId) {
|
||||
startDirectChat(sslId);
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
const m = require('mithril');
|
||||
const Data = require('network/network_data');
|
||||
const peopleUtil = require('people/people_util');
|
||||
const chatPreviewText = require('chat/chat_preview');
|
||||
const {
|
||||
State,
|
||||
startDirectChat,
|
||||
getOnlineSslId,
|
||||
setOwnCustomStateString,
|
||||
setOwnStatus,
|
||||
markDirectChatRead,
|
||||
} = require('network/network_state');
|
||||
|
||||
function formatRelativeTime(ts) {
|
||||
@ -132,12 +134,9 @@ const FriendsList = () => {
|
||||
const allGpgEntries = Object.entries(Data.gpgDetails || {});
|
||||
|
||||
// Compute active chats count
|
||||
let activeChatsCount = 0;
|
||||
let unreadChatsCount = 0;
|
||||
allGpgEntries.forEach(([gpgId]) => {
|
||||
const hist = State.chatHistoryMap && State.chatHistoryMap[gpgId];
|
||||
if (hist && hist.lastMsg) {
|
||||
activeChatsCount++;
|
||||
}
|
||||
unreadChatsCount += State.unreadChatCount[gpgId] || 0;
|
||||
});
|
||||
|
||||
let displayFriends;
|
||||
@ -199,7 +198,7 @@ const FriendsList = () => {
|
||||
[
|
||||
m('i.fas.fa-comments'),
|
||||
' Chats',
|
||||
activeChatsCount > 0 && m('span.segment-badge', activeChatsCount),
|
||||
unreadChatsCount > 0 && m('span.segment-badge', unreadChatsCount),
|
||||
]
|
||||
),
|
||||
m(
|
||||
@ -240,6 +239,7 @@ const FriendsList = () => {
|
||||
State.selectedFriendGpgId = gpgId;
|
||||
State.activeTab = 'chat';
|
||||
State.mobilePane = 'detail';
|
||||
markDirectChatRead(gpgId);
|
||||
const sslId = getOnlineSslId(gpgId);
|
||||
if (sslId) startDirectChat(sslId);
|
||||
},
|
||||
@ -262,10 +262,12 @@ const FriendsList = () => {
|
||||
},
|
||||
friend.name
|
||||
),
|
||||
m('.chat-last-msg', hist ? hist.lastMsg : ''),
|
||||
m('.chat-last-msg', hist ? chatPreviewText(hist.lastMsg) : ''),
|
||||
]),
|
||||
m('.chat-meta', [
|
||||
hist && hist.lastTime && m('.chat-time', formatRelativeTime(hist.lastTime)),
|
||||
(State.unreadChatCount[gpgId] || 0) > 0 &&
|
||||
m('.chat-unread-badge', State.unreadChatCount[gpgId]),
|
||||
]),
|
||||
]
|
||||
);
|
||||
|
||||
@ -26,6 +26,7 @@ const State = {
|
||||
gxsIdToDetailsMap: {},
|
||||
gxsIdentities: [],
|
||||
chatHistoryMap: {}, // gpgId -> { lastMsg, lastTime }
|
||||
unreadChatCount: {}, // gpgId -> unread messages received during this session
|
||||
currentChatPeerId: null,
|
||||
chatMessages: [],
|
||||
chatInputMsg: '',
|
||||
@ -186,6 +187,13 @@ function loadGxsIdentities() {
|
||||
function startDirectChat(sslId) {
|
||||
State.currentChatPeerId = sslId;
|
||||
State.chatMessages = [];
|
||||
const normalizedSslId = String(sslId || '').toLowerCase();
|
||||
const matchingFriend = Object.entries(Data.gpgDetails || {}).find(([, friend]) =>
|
||||
((friend && friend.locations) || []).some(
|
||||
(location) => String(location.id || '').toLowerCase() === normalizedSslId
|
||||
)
|
||||
);
|
||||
if (matchingFriend) markDirectChatRead(matchingFriend[0]);
|
||||
loadDirectChatMessages();
|
||||
loadRecentDirectChatHistory();
|
||||
}
|
||||
@ -213,36 +221,33 @@ function preloadNetworkChatHistory() {
|
||||
gpgIds.forEach((gpgId) => {
|
||||
if (!gpgId || gpgId === '0000000000000000') return;
|
||||
|
||||
const privatePeerId = {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: 1, // PRIVATE
|
||||
peer_id: gpgId,
|
||||
distant_chat_id: '00000000000000000000000000000000',
|
||||
lobby_id: { xstr64: '0' },
|
||||
};
|
||||
const friend = Data.gpgDetails[gpgId];
|
||||
const sslIds = Array.from(new Set(
|
||||
((friend && friend.locations) || []).map((location) => location.id).filter(Boolean)
|
||||
));
|
||||
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsHistory/getMessages',
|
||||
{
|
||||
chatPeerId: privatePeerId,
|
||||
loadCount: 20,
|
||||
},
|
||||
(msgData, success) => {
|
||||
if (success && msgData && msgData.msgs) {
|
||||
const userMsgs = msgData.msgs.filter(
|
||||
(m) => !m.isSystem && !isSystemMsg(m.message || m.msg)
|
||||
);
|
||||
if (userMsgs.length > 0) {
|
||||
const last = userMsgs[userMsgs.length - 1];
|
||||
State.chatHistoryMap[gpgId] = {
|
||||
lastMsg: last.message || last.msg || '',
|
||||
lastTime: last.sendTime || last.recvTime || Math.floor(Date.now() / 1000),
|
||||
};
|
||||
m.redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
Promise.all(sslIds.map((sslId) => new Promise((resolve) => {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsHistory/getMessages',
|
||||
{ chatPeerId: directChatId(sslId), loadCount: 20 },
|
||||
(msgData, success) => resolve(
|
||||
success && msgData && Array.isArray(msgData.msgs) ? msgData.msgs : []
|
||||
)
|
||||
).catch(() => resolve([]));
|
||||
}))).then((messageGroups) => {
|
||||
const userMsgs = messageGroups.flat().filter(
|
||||
(message) => !message.isSystem && !isSystemMsg(message.message || message.msg)
|
||||
).sort(
|
||||
(a, b) => (a.sendTime || a.recvTime || 0) - (b.sendTime || b.recvTime || 0)
|
||||
);
|
||||
if (userMsgs.length === 0) return;
|
||||
const last = userMsgs[userMsgs.length - 1];
|
||||
State.chatHistoryMap[gpgId] = {
|
||||
lastMsg: last.message || last.msg || '',
|
||||
lastTime: last.sendTime || last.recvTime || Math.floor(Date.now() / 1000),
|
||||
};
|
||||
m.redraw();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -251,24 +256,47 @@ function loadDirectChatMessages() {
|
||||
const messagePeerId = chatMessage.chat_id && chatMessage.chat_id.peer_id
|
||||
? rs.idToHex(chatMessage.chat_id.peer_id)
|
||||
: '';
|
||||
if (
|
||||
chatMessage.chat_id &&
|
||||
chatMessage.chat_id.type === 1 &&
|
||||
messagePeerId === State.currentChatPeerId
|
||||
) {
|
||||
State.chatMessages.push(chatMessage);
|
||||
if (State.selectedFriendGpgId) {
|
||||
State.chatHistoryMap[State.selectedFriendGpgId] = {
|
||||
lastMsg: chatMessage.msg || chatMessage.message || '',
|
||||
lastTime: chatMessage.sendTime || chatMessage.recvTime || Math.floor(Date.now() / 1000),
|
||||
};
|
||||
if (!chatMessage.chat_id || chatMessage.chat_id.type !== 1 || !messagePeerId) return;
|
||||
|
||||
const normalizedPeerId = messagePeerId.toLowerCase();
|
||||
const matchingFriend = Object.entries(Data.gpgDetails || {}).find(([, friend]) =>
|
||||
((friend && friend.locations) || []).some(
|
||||
(location) => String(location.id || '').toLowerCase() === normalizedPeerId
|
||||
)
|
||||
);
|
||||
const gpgId = matchingFriend ? matchingFriend[0] : null;
|
||||
|
||||
// Update the Chats list for every private message, not only for the
|
||||
// conversation that happens to be visible.
|
||||
if (gpgId) {
|
||||
State.chatHistoryMap[gpgId] = {
|
||||
lastMsg: chatMessage.msg || chatMessage.message || '',
|
||||
lastTime: chatMessage.sendTime || chatMessage.recvTime || Math.floor(Date.now() / 1000),
|
||||
};
|
||||
|
||||
const isOpenConversation =
|
||||
State.activeTab === 'chat' &&
|
||||
State.selectedFriendGpgId === gpgId &&
|
||||
normalizedPeerId === String(State.currentChatPeerId || '').toLowerCase() &&
|
||||
(window.innerWidth > 700 || State.mobilePane === 'detail');
|
||||
if (chatMessage.incoming === true && !isOpenConversation) {
|
||||
State.unreadChatCount[gpgId] = (State.unreadChatCount[gpgId] || 0) + 1;
|
||||
}
|
||||
m.redraw();
|
||||
}
|
||||
|
||||
if (normalizedPeerId === String(State.currentChatPeerId || '').toLowerCase()) {
|
||||
State.chatMessages = mergeDirectChatMessages(State.chatMessages.concat(chatMessage));
|
||||
scrollChatToBottom();
|
||||
}
|
||||
m.redraw();
|
||||
};
|
||||
}
|
||||
|
||||
function markDirectChatRead(gpgId) {
|
||||
if (!gpgId || !State.unreadChatCount[gpgId]) return;
|
||||
State.unreadChatCount[gpgId] = 0;
|
||||
}
|
||||
|
||||
function directChatId(peerId) {
|
||||
return {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
@ -412,6 +440,7 @@ module.exports = {
|
||||
getOnlineSslId,
|
||||
preloadNetworkChatHistory,
|
||||
loadDirectChatMessages,
|
||||
markDirectChatRead,
|
||||
loadRecentDirectChatHistory,
|
||||
loadAllDirectChatHistory,
|
||||
sendDirectChatMessage,
|
||||
|
||||
@ -2,6 +2,7 @@ const m = require('mithril');
|
||||
const rs = require('rswebui');
|
||||
const widget = require('widgets');
|
||||
const peopleUtil = require('people/people_util');
|
||||
const chatPreviewText = require('chat/chat_preview');
|
||||
const ownIdsLayout = require('people/people_ownids');
|
||||
const { CreateIdentity } = ownIdsLayout;
|
||||
const {
|
||||
@ -208,7 +209,9 @@ const PeopleSidebar = () => {
|
||||
const hist = State.chatHistoryMap[gxsId];
|
||||
const lastTS = hist ? hist.lastTime : (itemDetails ? get64Num(itemDetails.mLastUsageTS) : 0);
|
||||
const relativeTimeStr = formatRelativeTime(lastTS);
|
||||
const lastMsgText = hist && hist.lastMsg ? hist.lastMsg : (itemIsOwn ? 'My Identity' : itemIsContact ? 'Saved Contact' : 'Distant Chat');
|
||||
const lastMsgText = hist && hist.lastMsg
|
||||
? chatPreviewText(hist.lastMsg)
|
||||
: (itemIsOwn ? 'My Identity' : itemIsContact ? 'Saved Contact' : 'Distant Chat');
|
||||
|
||||
if (State.mainTab === 'chats') {
|
||||
return m(
|
||||
|
||||
@ -335,6 +335,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-unread-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-top: .2rem;
|
||||
padding: 0 .35rem;
|
||||
border-radius: 999px;
|
||||
background: #0284c7;
|
||||
color: #fff;
|
||||
font-size: .7rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Shared phone master/detail header. It is intentionally absent on desktop. */
|
||||
.mobile-pane-header,
|
||||
.mobile-graph-shortcut {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user