- Enable Ctrl+Enter (and Cmd+Enter) to insert a newline without sending
- Prevent plain Enter send handler when modifier keys (Ctrl/Cmd) are pressed
- Support dynamic multiline auto-resizing across Lobby, People, and Network chats
- Preserve Shift+Enter standard newline behavior
- Prevent 3D inset box-shadow from bleeding into close and icon buttons
- Fix missing glyph in Chat History Browser by switching far.fa-comments to fas
- Add .far fallback mapping to Font Awesome Solid font family
- Add hover styling for modal close controls
In Chat → Details, every other participant now has three buttons:
- Chat — starts a private chat
- Mail — opens mail compose for that identity
- Details — opens their People profile
On a phone, scrolling a conversation to its top to read further back is
the same gesture as the browser's pull-to-refresh once the pane cannot
scroll any more: the page reloaded in the middle of the conversation.
The message panes and the page containers now contain the overscroll,
and the document never turns it into a reload.
The private chat pane showed the last fifty messages of the tunnel just
opened and nothing else: no way to scroll further back, and nothing of
what was said with the same contact under another of our identities or
over direct chat -- all of that only in the history browser.
The pane now reads the same sources as the browser: one distant chat
file per own identity (the tunnel id is derived from the pair) and the
direct chat file of every location of the friend behind the identity.
The first load takes a page of each; scrolling near the top asks every
source for a bigger slice, the same mechanism as the chat rooms since
v157, the reader kept in place while the slice is inserted above. Once
every source answers with fewer than asked, nothing older is fetched.
"Tunnel is secured. You can talk!" was appended to the conversation
without scrolling to it, so on a phone it sat below the fold and the
tunnel looked stuck on connecting. Same for "Your partner closed the
conversation". Scroll when the line is really new (the helper drops
duplicates) and the contact is still the one on screen.
Selecting a contact from a chat room (setSelectedId) changed the
selected identity but left the page-wide chat fields -- chatPid, status,
messages -- pointing at the previous contact's tunnel. The People page
then started its status poll on that pid at mount; the core no longer
knew it, two "false" answers later the poll wrote "tunnel is gone" into
the NEW contact's session and flagged the conversation as ended, while
the real tunnel was still coming up in the desktop window. On a slow link
the poll answers land late enough to overwrite the state of the
conversation opened meanwhile.
setSelectedId now resets the page chat state to the selected contact's
own session; the mount-time poll only starts on a pid that belongs to the
selected contact; and every poll answer is dropped unless it is about the
pid and the contact it was asked for. The "ended" card also says what
happened when the tunnel went away by itself, instead of "you closed it".
Every mount of the Network, People, Home, Statistics or Graph pages
redid Data.refreshGpgDetails(): getFriendList, then for every location
getPeerDetails, isOnline, getCustomStateString and getStatus -- four
requests per location, fired together through Promise.all. With two
thousand locations that is eight thousand requests filling the browser's
six sockets, and on a phone behind a slow link every interactive request
-- opening a distant chat, the tunnel status poll, a chat message --
queued for minutes behind them. The Debug page showed it as a scroll of
getPeerDetails.
Now: one getOnlineList replaces the per-location isOnline; the status
string and status value are asked only for peers that are online; the
getPeerDetails sweep runs three at a time, the list filling as answers
land on a first load; the result is kept for five minutes, later mounts
only refreshing the online flags with that one request, and concurrent
callers share the sweep in flight. Adding or removing a friend forces a
full sweep.
The API timings went into the phone status sheet in v162; a page of
their own reads better and works in the wide layout too. /debug, in the
rail and in the phone's More sheet: the web UI build and the core
version with its round trip, a Reload button, requests in flight, the
last sendChat round trip, the five slowest and the twenty last requests,
the event stream (bytes, last event, reconnections), and a reset. The
status sheet keeps the version label and its Reload button only.
v163 settled on any tunnel entry the core still held for the contact,
dead ones included -- a peer-opened tunnel that died stays in the core's
table with a "down" status it cannot re-dig itself -- so opening a chat
then waited on it for good, and since nothing called
initiateDistantChatConnexion any more, no desktop window popped either.
Only a tunnel that can talk (status 2) now decides the identity to chat
as; the conversation is then always opened through
initiateDistantChatConnexion, which hands back the same tunnel id for an
existing pair and pops the desktop window as before.
The version label moves into one constant, shown in the phone header
next to the brand -- the status sheet was the only place carrying it --
and the status sheet gets a Reload button beside it: the page keeps the
code it loaded until reloaded, and phone browsers hide that action.
A tunnel to a contact can exist without the web UI knowing: opened from
the desktop window, or by the peer, and possibly under another of our
identities. The page always asked the core for a tunnel from its own
default identity (the first one), so for any other identity the core dug
a second tunnel and the phone sat on "Connecting" while the desktop
showed the first one green.
The tunnel id is sha1(sorted(own || peer)), computable here, so before
opening one the page now asks getDistantChatStatus for the id of every
own identity and adopts the live tunnel (status 2), else any the core
still holds, switching "Chatting as" to that identity. An explicit
identity switch keeps opening what the user asked for. Late answers for a
contact the user has left are dropped.
A message typed on a phone far from the core takes ten to twenty seconds
to show up, and nothing on the phone says where the seconds go: a slow
request on its own points at the core, many requests pending at once
points at the browser's six sockets -- one of them held by the event
stream -- or at the link in between.
rswebui.js now counts requests in flight, times every request, keeps the
last /rsChats/sendChat round trip and the five slowest requests, and
stamps the event stream (bytes received, last event, reconnections). The
phone status sheet shows them under the version label.
Below 900px the participants column of a room was simply display: none,
and the only way to a participant's menu -- Start private chat, Send
Message, mute, opinions -- was a right click, which a phone does not have.
The Details tab lists the room's known identities, but from the static
getChatLobbyInfo answer and without the menu.
Add a Participants button to the room header, shown below 900px only,
that opens the same column as a sheet over the messages, with a close
button in its title. Where the sheet applies, or on a device without
hover, a tap on a participant opens its menu; the right click keeps
working elsewhere. The sheet closes when a menu item leaves the page and
whenever another room is loaded.
A chat room message sent from the desktop GUI (or any other client of the
same core) never appeared live in the web UI; it only showed up after
leaving and reopening the room, through the history.
The core posts a peer's message twice on RsEventType::CHAT_SERVICE, as an
RsChatServiceEvent {mEventCode: CHAT_MESSAGE_RECEIVED, mMsg} and as an
RsChatMessageEvent {mChatMessage} (p3ChatService::handleRecvChatMsgItem).
Our own messages are posted once, as the RsChatServiceEvent only
(DistributedChatService::sendLobbyChat, p3ChatService::sendChat). The
event queue handler only read mChatMessage, so the RsChatServiceEvent fell
through to the administrative branch and was dropped.
Read the message from mMsg as well, but only when incoming === false: a
peer's message must keep arriving once, since the room and direct chat
unread counters are bumped before the receivers dedup by message key.
Our own message typed in the web UI itself still arrives (echo + event)
and is dropped by that dedup.
Reported: Decline answers "RetroShare could not decline this invitation".
denyLobbyInvite() returns false for exactly one reason -- the id is not in the
core's invite queue, and DistributedChatService prints "lobby invite not in
cache" when that happens. That queue lives in memory only, so a core restart
empties it while this list still shows what it held before; the entry then can
be neither accepted nor refused, and it keeps the Chat badge lit for good, which
is what the Decline button was added to prevent.
So a refusal the core does not recognise now removes the invitation here anyway
and re-reads the queue, and says so. A request that gets no answer at all is
kept apart: nothing was decided, so nothing is dropped.
The Chat badge now counts pending room invitations, but the web UI only offered
to accept one: denyLobbyInvite() exists in the core and was called nowhere. An
invitation left alone therefore kept the badge lit for good, with no action able
to clear it. It can be declined now -- the entry leaves the room list with it,
since it came from the invitation and not from the nearby lobbies.
An invitation also carries no participant count: ChatLobbyInvite has lobby_id,
peer_id, lobby_name, lobby_topic and lobby_flags, and total_number_of_peers only
exists on the records the nearby list returns. The detail panel claimed "0 users"
for every invited room, and the join-failure message read the same missing field
to conclude the room was "no longer advertised by an online participant" --
a wrong diagnosis when the real cause was a refused identity.
The version label also went back to v154 on the way in: the four merges of #23
to #26 each resolved main.js by keeping the lower number, so the branch says
v154 while carrying the code of v157. Set to v158 rather than restoring v157,
since the composer spacing and the invitations landed after it.
A room opens on its last 20 messages and that was all there ever was: reaching
the top of the pane loaded nothing, so anything older was only reachable through
the separate history browser.
Scrolling near the top now asks for a larger slice. p3HistoryMgr::getMessages
takes a count and nothing else -- no cursor, no "before this message" -- and
always answers with the newest ones, so the only way to reach older text is to
ask for more and let addMessages() drop what is already held. It re-sends what
we have, which is the price of that API; the slice is small, and the core keeps
ten days at most anyway.
An answer shorter than the count asked for means there is nothing older left,
and the pane stops asking. The opening slice stays at 20: every room opening
pays for it, and on a phone each request is a fresh connection on a core that
answers one at a time.
Older messages are inserted above what is on screen, which would push the
conversation down by their height; that height goes back into scrollTop, so the
reader does not move while a slice lands.
The message pane scrolled to the bottom from its onupdate, unconditionally. That
hook runs on every redraw of the chat hub -- an incoming message, a poll answer,
a keystroke in the composer -- so scrolling up to read anything older was undone
a few tens of milliseconds later, every time.
It now follows the reader: the pane stays pinned while it is already at the
bottom, stops as soon as it is scrolled away from it, and pins again when it
comes back. Sending a message still jumps down whatever the state, and switching
room opens on its last message, since the pane is reused rather than recreated
and its oncreate does not run again.
The scroll handler sets event.redraw = false: mithril redraws after every
handler by default, and this one fires continuously while the pane is dragged.
markReadLocally() clears the whole 0xf0 nibble, but RS_MSG_TRASH is 0x20 and
lives inside it (rsmail.h). Opening a message from the Trash therefore cleared
its trash flag as well, and the row showed as an ordinary read mail until the
next load. Only the two unread bits are cleared now.
The badge itself is read from the navigation view, so unreadCount() ran a full
pass over the inbox on every redraw -- once for the rail and twice more for the
bottom bar, which asks for the value twice per item. The count is kept instead,
recomputed when the inbox changes: after a load, and after a message is marked
read.