Commit Graph

496 Commits

Author SHA1 Message Date
jolavillette
2c38f04fa1 webui v141: stop the endless getIdDetails retry, and one copy path
fetchIdDetails() restored `undefined` after its five attempts, which is the
very value that makes it fire a request -- and two of its callers sit inside
a view (people_sidebar's filter and map). Every redraw therefore restarted the
whole six request chain, forever, for any identity the core never resolves.
Mark the give-up state with `null` instead, like the loading state: attempted,
not to be asked again.

The copy icon still ran the old inline execCommand path, so copyId() was only
reachable through shareId()'s fallback and its clipboard call could reject with
no feedback at all. The icon now calls copyId(), the Clipboard API failure falls
back to execCommand, and the popup reports a refused copy instead of claiming
success. Same role/tabindex/keyboard treatment as the share icon next to it.
2026-08-16 09:45:02 +02:00
defnax
4b523cb8ec Fixed issue on phone to view parcipants list 2026-08-15 22:59:21 +02:00
defnax
71688feac2 Fixes Ui issues
- Correct full-certificate friends being incorrectly marked “Pending validation.”
- Refresh identity lists immediately after creating an identity.
Update open chat-room and distant-chat identity selectors automatically.
- Replace the broken signed-identity password popup with a responsive mobile form.
- Allow creation of the first signed identity without requiring an existing signed identity.
Filter temporary all-zero GXS IDs.
- Wait for RetroShare to expose the real generated identity ID.
- Retry identity-detail loading when RetroShare temporarily returns empty data.
- Display the fetched nickname instead of “Unknown.”
2026-08-15 22:43:19 +02:00
defnax
1c615bdb89 Fixing adding friends via Short ID 2026-08-15 17:33:08 +02:00
defnax
fceefc95bb fixed popup window for phone 2026-08-15 16:37:58 +02:00
jolavillette
241380ff2e webui v140: adapt the inside of the pages to a phone
Loaded last from main.scss, so it wins over the page stylesheets without
!important. Five sections, all scoped to the phone: multi column grids collapse,
wide tables become cards, the statusbar scrolls instead of truncating, form
fields stop triggering the iOS zoom, and anything only reachable on :hover gets
a touch fallback.

Not here any more, because improvements_v2 now does it itself: the navigation
(bottom tab bar and status sheet in main.js) and the master-detail switch of the
network, people and chat pages (.mobile-detail-open, driven from their own
state). This layer starts where those stop.

The two JS changes are the .mails-head and .comments-head markers those tables
need: mithril builds the DOM through the DOM API, so it never gets the implicit
<tbody> the HTML parser would insert, and neither tr:first-child nor a
`> tbody >` path can find the header row.
2026-08-15 15:44:43 +02:00
jolavillette
0ecb4649d2 webui: declare the phone breakpoints in a single place
The values are not new: they document the ones already hardcoded across the
stylesheets. The mobile mixin covers portrait phones and landscape ones, which
are wide enough to escape a width-only breakpoint but far too short for the
layouts the page stylesheets switch to.
2026-08-15 14:50:29 +02:00
defnax
209adb6564 Fixed Network active chats
- Fixed. Chat previews
- Implemented unread chat counts
2026-08-15 12:27:39 +02:00
defnax
c7011ff280 Improved chat, network & people pages for mobile phones 2026-08-15 11:50:17 +02:00
defnax
01ead3a0ce Fixed issue with mail composer button & mail composer height 2026-08-14 19:42:23 +02:00
defnax
46876eac9a improvements for mobile phone 2026-08-14 18:48:52 +02:00
jolavillette
2182afe5aa webui v139: bump the label for the network graph pass 2026-08-14 13:39:01 +02:00
jolavillette
007dc5cf8c one cleanRetroshareId, in rswebui
The Add friend wizard and the location details dialog each carried their own
copy, character for character the same except that one trimmed after decoding
and the other did not. The shared version keeps the trim: a RetroShare ID pasted
from a link often arrives with a trailing newline, and the dialog was displaying
it.
2026-08-14 13:38:52 +02:00
jolavillette
4831b6f3e6 network graph: keep the graph across visits, and let the newest load win
The tab only mounts the component while it is the active tab, so leaving the
graph and coming back rebuilt it from scratch: the discovery requests again, and
a layout that costs up to 729 ms. The graph state now lives at module level and
oninit only reloads when there is nothing to show or the last load is over a
minute old. The zoom, the search and the friendship level survive the trip too.

Changing the friendship level starts a load without waiting for the one already
running -- the Redraw button is disabled while loading, the select is not -- and
the two do not necessarily finish in order, so the slower one could overwrite
the fresher result. Each load now takes a token and drops its results if a newer
one has started since.
2026-08-14 13:38:04 +02:00
jolavillette
82a8c97ca1 network graph: discover friends six at a time
loadGraph() fired one /rsGossipDiscovery/getDiscPgpFriends per direct friend at
once, then one per second level peer at once -- up to two hundred parallel
requests from a single tab.

A browser opens about six connections per host. Asking for two hundred does not
make them arrive sooner: they queue in the tab, and past a certain point they
start failing outright. That is the request storm which made the channel list
unusable before it was batched.

Both fan-outs now go through discoverInBatches(), six at a time.
2026-08-14 13:36:48 +02:00
jolavillette
1a9442fae2 network graph: relayout when the slider is released, not on every pixel
layoutGraph() runs 140 iterations of an O(n^2) force loop. Measured by calling
it standalone with the same constants:

     20 nodes     24 ms
     50 nodes     57 ms
    100 nodes    199 ms
    200 nodes    729 ms   (NODE_LIMIT)

The edge length slider called it from oninput, which a range input fires dozens
of times per drag, each call blocking the main thread. Dragging it on a level 2
graph froze the tab for several seconds.

oninput now only moves the label, which is what has to stay live, and onchange
does the layout once, on release.
2026-08-14 13:36:15 +02:00
defnax
307981e960 Added network graph page 2026-08-14 11:46:11 +02:00
defnax
a0920d297d Added comments badge on the channel Posts thumbnail 2026-08-14 11:21:25 +02:00
defnax
cddee00b69 Added to show avatars for comments 2026-08-14 11:12:20 +02:00
defnax
7e1358e988 removed not needed label 2026-08-14 10:59:42 +02:00
defnax
622b48bbf5 Added option to change your Status in your own status dot 2026-08-14 10:54:07 +02:00
defnax
46a9eb38b2 Improved the Add Friend wizard to focus on textual RetroShare IDs.
Changes include:
- RetroShare ID paste field is now the primary action.
- Supports raw IDs and rsInvite= URL-formatted IDs.
- Empty submissions are disabled.
- Clearer invalid-ID messaging.
- File import and drag/drop remain available as secondary options.
- More polished, responsive wizard styling.
- Accepts text files even when the browser omits their MIME type.
2026-08-14 10:27:23 +02:00
jolavillette
c3f8115a19 forums: the emoji picker was drawing white emojis on a white panel
Clicking the emoji button opened the panel, and clicking inside it did insert an
emoji, but the panel looked empty: the emojis were being drawn, in white, over
white.

The global `button` rule applies the button() mixin, which sets `color: white`
to sit on a coloured background. The picker resets the parts of it that clash --
border, background, box-shadow -- but not the colour, and its own background is
#fff. The category row above the grid was invisible for the same reason.

Found by testing in a browser: nothing in the sources says a button is white
until you follow the mixin.
2026-08-13 14:28:59 +02:00
jolavillette
2ed361626f login: honour the ?Url= parameter again
urlParams.get('Url') || window.location.protocol === 'file:'
      ? 'http://127.0.0.1:9092'
      : <origin>

=== binds tighter than ||, so the test reads

    (Url || protocol === 'file:') ? default : origin

and passing ?Url= made the condition true, which selected the hardcoded default
and discarded the value given -- the one case the parameter exists for. Username
and Password, read the same way just above, worked; only Url did not.

Parenthesised so the parameter wins when present, and the file:// and served
cases keep the behaviour they had.
2026-08-13 13:51:46 +02:00
jolavillette
659f612e5d webui v138: bump the label for the audit pass 2026-08-13 13:50:09 +02:00
jolavillette
5efd9a24ca a failed request resolves to an empty answer, not to undefined
rsJsonApiRequest resolves undefined when a request fails. Around forty call
sites go straight for res.body.retval, so every failure threw a TypeError --
inside an onclick most of the time, where nothing catches it: the button does
nothing at all, and the console shows a stack about `body` rather than a failed
request.

It now resolves the same shape as a real answer, with an empty body. Every
defensive check in the code base tests res.body or res.body.retval, so an empty
body still reads as a failure to all of them, including the batch loaders that
key their split-retry off it.
2026-08-13 13:50:09 +02:00
jolavillette
db36a05a74 network: cache the location details, and stop waiting on a catch that never fires
The version and the short invite of a node do not change while the web UI is
open, but the dialog asked the core again on every open, showing "Loading..."
each time. They are now cached by node id.

The two .catch() handlers could not do what they were written for:
rsJsonApiRequest never rejects, it resolves undefined when a request fails. They
only ever ran by accident, when reading .body of that undefined threw a
TypeError inside the .then. The failure is now read off the resolved value,
where it actually is.
2026-08-13 13:50:09 +02:00
jolavillette
38684d7f0c eslint: drop the initialisers that are always overwritten
The last three no-useless-assignment errors. Each declares a value that every
path reassigns before reading it: the exhaustive if/else of customState, the
network/chats branch of displayFriends, and the quality loop of result, which
runs at least once and throws below it.

The web UI now lints clean: 0 errors, 0 warnings, where the branch point had 17.
2026-08-13 13:50:09 +02:00
jolavillette
51720e3024 remove the components left dead by the chat hub and comment rewrites
eslint reported them and nothing references them: they are the previous
generation of UI, superseded in place.

channels/channel_view.js
  displaycomment() and the AddComment form it opened, replaced by renderComment()
  and ChannelComments. displaycomment only referenced itself, recursively, which
  is why it looked used.

chat/chat.js
  LayoutSingle, the single chat room layout that predates the hub, and with it
  LobbyList, Lobby, SubscribedLobbies and PublicLobbies, which nothing else
  called. Six names imported from chat_state were unused as well.

Kept as its own commit so it can be reverted alone if any of it turns out to be
wanted again.
2026-08-13 13:50:09 +02:00
jolavillette
2ea0422039 channels: stop redownloading a channel on every visit
Since the list was reduced to metadata, ChannelView.oninit fetches the content
of the channel being opened. oninit runs again on every visit though, with no
guard, so stepping in and out of a 2000 item channel redownloaded all of it,
images included, each time.

The content is now pulled only when it is missing from memory or older than a
minute, so posts published meanwhile still appear without forcing the user to
reload the page. The call sites that publish or delete call updatedisplaychannels
directly and keep refreshing unconditionally.

The timestamps live at module level: the component is rebuilt at every visit, a
field of it would forget immediately.
2026-08-13 13:50:09 +02:00
jolavillette
b62095aaf3 forums: measure the composer limit in bytes, and build the body once
Three defects in the new thread composer.

The 199 000 limit of a GXS message is a *byte* count, but the composer compares
it against String.length, which counts UTF-16 units: an accented letter is two
bytes for one unit, an emoji four bytes for two. With an emoji picker one click
away in that very toolbar, the counter can report room left on a message the
core will refuse. It now measures UTF-8 through TextEncoder, and says bytes
rather than characters.

postBody() was called five times per render -- twice for the class, twice for
the counter text, once for the disabled state -- and it re-escapes the message
and re-joins every inline image, each of which is up to 175 KB of base64. That
ran on every global redraw, which the statusbar triggers continuously, while the
user is typing. It is now built once per pass.

pollFileHash() re-arms itself every 500 ms for up to a minute. Closing the
composer left it running: it kept polling and redrawing a component that is no
longer mounted. onremove now stops it.
2026-08-13 13:50:09 +02:00
defnax
a070df6ff2 Improved the Forum thread/reply composer: 2026-08-13 13:27:36 +02:00
jolavillette
059de45e8a webui v137: dvh for the full screen overlays
Mobile browsers count the collapsible URL bar in 100vh, so an overlay sized that
way is taller than the visible area: its bottom, where the buttons usually are,
sits under the fold and cannot be reached. dvh follows the bar as it retracts.

Three sites, all fixed overlays: the global modal backdrop (#popupmessage) and
the two chat dialog overlays. The vh line is kept above the dvh one as a
fallback for engines that do not know the unit and would otherwise drop the
declaration entirely.

styles.css regenerated with the pinned sass 1.97.3.
2026-08-13 12:32:28 +02:00
jolavillette
d84acf7c97 webui v136: an HTTP error is not a lost connection
Three defects of the same family, in the loading path.

1. rsJsonApiRequest sets connectionState.status = false on *any* status other
   than 200. But an answer, whatever its code, proves the core is there. A 404
   on an endpoint this build does not expose, a 401 on a stale password: none of
   them is a lost connection. Only status 0, no HTTP response at all, is.

   This is visible today: getBoardPostSummaries only exists in an unmerged
   libretroshare branch, so every board load 404s on a stock core and the status
   LED blinks red before the fallback runs. Any optional endpoint we probe from
   now on has the same effect.

   The flag is now false only on status 0, and the last HTTP status is recorded
   in extract() so it survives a body that fails to parse.

2. Reaching .catch() after a valid 200 means the response was cut short, not
   that the core went away. connectionState now stays true there. That matters
   because a truncated response is exactly what the JSON API produces when it
   cannot flush a large answer in time, and the loaders react to that flag.

3. The channel loader splits a failed batch in two until the offending post is
   isolated, which is right when the response was too large, and catastrophic
   when the core is unreachable: every half fails too, so one batch of 25 turns
   into 2N-1 = 49 doomed requests, ~4000 for a 2000 item channel. It now stops
   splitting when connectionState is false, which points 1 and 2 made accurate.

   The board loader had no splitting at all: it ignored the boolean updateContent
   already returned, so a truncated batch of 25 posts vanished on a single
   console.warn. It now uses the same helper as the channels.
2026-08-13 12:30:45 +02:00
defnax
d58d27faf7
Merge pull request #9 from jolavillette/fix/modal-vnode-reuse-for-121
Clone modal content all the way down
2026-08-13 12:27:21 +02:00
jolavillette
be039e2448 webui v135: clone modal content all the way down
A vnode carries the DOM node it owns, so the same one must not be rendered
twice. popupMessage is handed a ready made vnode and now mounts it, which
re-renders it on every global redraw, so freshVnode() rebuilds it on each pass.

It only rebuilt the root though: `m(vnode.tag, vnode.attrs, vnode.children)`
passes the children array by reference, and updateNodes() starts with

    if (old === vnodes ...) return

so mithril skips the entire subtree. Everything below the first level of a modal
is therefore frozen at its first render — which goes unnoticed today because the
popups built as plain vnode trees are all static messages, and everything that
has to update is passed as a component. It is a trap for the next one.

The clone is now recursive, with the three tags that are not selectors handled
through their own factory: '<' is m.trust, and rebuilding it with m() would
silently turn trusted html into an empty div, since '<' matches nothing in the
selector parser; '[' is m.fragment; '#' is a text vnode whose children is the
string itself.

Checked that no popup built as a plain tree contains an input, textarea or
select, so nothing starts having its value re-applied under the user's fingers:
every form modal goes through a component, which re-renders on its own.

The version label assumes the loader robustness PR lands first.
2026-08-13 12:15:25 +02:00
defnax
21716677d8 Fixed the subscribed Forum page layout
- Added a default Forum thumbnail to the forum details card.
- Added a default Board thumbnail for boards without an uploaded image.
- Added a default Channel thumbnail when no image is set.
2026-08-13 12:03:23 +02:00
jolavillette
03616fc38b webui v133: never put a userMap entry straight into a view
Opening a forum threw, and every redraw after it threw again:

    TypeError: Cannot read properties of undefined (reading 'view')
        at initComponent
    NotFoundError: Failed to execute 'removeChild' on 'Node'

userList.userMap holds {name, isContact} objects (rswebui.js:300 and :317),
not strings. userList.username() is the accessor that unwraps them:

    const name = typeof entry === 'object' ? entry.name : entry;

but several views read the map directly and handed the raw object to mithril:

    fauthor = rs.userList.userMap[forumDetails.author];   // forum_view.js
    ...
    m('p', m('b', 'Admin: '), fauthor)

Vnode.normalize returns anything that is `typeof === 'object'` untouched, and
createNode sends every non-string tag to createComponent, so the object was
treated as a component and initComponent dereferenced `vnode.tag.view` on
undefined. Once a redraw throws inside createNodes the vdom no longer matches
the DOM, which is where the removeChild storm comes from: the statusbar, the
identity bulk fetch and the forum load all redraw, and all of them fail.

It only bites when the author is already in the identity cache, which is why it
looks intermittent: the render that breaks is the one triggered by fetchBulk's
redraw, when the entry flips from missing to object.

Same direct read, same crash, in the channel view (header author, and the
comment author cell) and in the board view. The identity selectors called
.toLocaleString() on the entry, which does not throw but prints
"[object Object]" as the option label.

All of them now go through username(), which returns the name when it is known
and the raw id otherwise, and which queues the missing ids for the next bulk
fetch on the way.
2026-08-13 11:24:16 +02:00
defnax
fe90759a55 Improved the Create Identity dialog
- Added persistent avatar preview
- Shows a deterministic jdenticon by default
- Jdenticon updates based on the entered identity name
- Added custom avatar selection
- Shows selected avatar immediately
- Added “Use default” to remove the custom avatar
- Custom avatar is sent for linked and pseudonymous identities
- Updated responsive desktop/mobile layout
2026-08-13 11:20:54 +02:00
defnax
bb40c8cbe5 Fixed the Location Details dialog height. 2026-08-13 11:01:46 +02:00
defnax
53d9a607f8 Added board post feature
Improved create channel post
2026-08-12 21:47:05 +02:00
defnax
fbf2d1c859 Improved forum create 2026-08-12 20:43:39 +02:00
defnax
ae33805f6c Improved Channel & Baord create 2026-08-12 20:06:41 +02:00
defnax
979b5ff902 Added tooltips feature for mail composer 2026-08-12 19:11:33 +02:00
defnax
1fa842eee3 Fixed Text alignment 2026-08-12 18:50:51 +02:00
defnax
6299503bb5 Fixed the forum post clipping.
Cause: the thread view created a second full-height .widget inside the page’s existing .widget. The nested height and overflow rules caused long posts to extend into a clipped area.
Changes:
- Removed the nested full-height widget
The main Forums widget is now the single vertical scroll area
- Added safe wrapping for long text
- Added horizontal scrolling for wide code blocks and tables
- Constrained images, videos, and embeds to the available width
2026-08-12 18:39:56 +02:00
defnax
e7e2c3365e Added to show Location datails 2026-08-12 18:24:54 +02:00
defnax
f670319331 Added to show pgp fingerprint 2026-08-12 17:53:35 +02:00
defnax
88aaf7cf92 Implemented true progressive loading for large Boards
Changes:
* Added getBoardPostSummaries() to libretroshare.
* Returns only lightweight top-level post metadata, excluding comments and votes.
* WebUI sorts summaries newest-first.
* Complete posts download in batches of 25.
* First page renders before older posts finish loading.
* Older RetroShare cores retain the existing getBoardAllContent fallback.
2026-08-11 18:34:49 +02:00
defnax
8ae71106e1 Fixed the large-channel loading problem.
Fixed the large-channel loading problem.

Changes:

- Channel lists now load only channel metadata, instead of downloading all posts, comments, votes, and images for every listed channel.
- Full content loads only after opening a channel.
- Reduced content batches from 200 items to 25.
- Failed oversized responses automatically split into progressively smaller requests.
- Added response validation to avoid crashes when RetroShare returns incomplete data.
2026-08-11 17:41:44 +02:00