From 1a2a31365a09a9a5cbd4a43fac6481f3c2f57d72 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 4 Oct 2026 23:40:47 -0700 Subject: Show popular Concord communities on the Messages screen --- src/features/messages.js | 42 ++++++++++++++++++++++++++++-------------- src/i18n.js | 6 +++++- 2 files changed, 33 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/features/messages.js b/src/features/messages.js index 0a0a536..a9b21b0 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -1858,13 +1858,13 @@ export function messagesFeature(ctx) { } // ---- community discovery: the Vector Hub's public listing as suggestions - // under Join. Fetched on demand (it's a megabyte, mostly icons) and kept + // on Messages and under Browse. Fetched when Messages opens and kept // for the session; every listing is a CORD-05 link, which is exactly what // the paste box takes, so a tap previews it like a pasted invite. const HUB_URL = 'https://vectorapp.io/api/hub'; const hub = { state: 'idle', list: [] }; async function loadHub() { - if (hub.state === 'loading' || hub.state === 'ready') return; + if (hub.state !== 'idle') return; hub.state = 'loading'; scheduleRepaint(); try { const res = await fetch(HUB_URL, { signal: AbortSignal.timeout(20_000) }); @@ -1882,12 +1882,16 @@ export function messagesFeature(ctx) { } catch { hub.state = 'error'; } scheduleRepaint(); } - function hubRows() { - const q = (ui.msgJoinText || '').trim().toLowerCase(); + function hubRows({ preview = false } = {}) { + const q = preview ? '' : (ui.msgJoinText || '').trim().toLowerCase(); const have = new Set(communities().map((jm) => (rooms.get(jm.community_id)?.folded?.metadata?.name || jm.name || '').toLowerCase())); - const rows = hub.list.filter((c) => !q || [c.name, c.description, c.category].some((x) => x.toLowerCase().includes(q))); + let rows = hub.list.filter((c) => !q || [c.name, c.description, c.category].some((x) => x.toLowerCase().includes(q))); + if (preview) rows = rows.filter((c) => !have.has(c.name.toLowerCase())).slice(0, 6); + if (!rows.length) return [h('div', { class: 'small muted' }, t(preview ? 'msgHubNoSuggestions' : 'msgNoMatches'))]; return rows.map((c) => h('div', { - class: 'item chat-thread-row clickable', onClick: () => joinFromText(c.invite), + class: 'item chat-thread-row clickable', role: 'button', tabindex: '0', + onClick: (e) => { e.stopPropagation(); joinFromText(c.invite); }, + onKeydown: (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); joinFromText(c.invite); } }, }, c.icon ? h('img', { class: 'chat-avatar hub-icon', src: c.icon, alt: '' }) : h('div', { class: 'chat-avatar fallback' }, c.name.slice(0, 2)), h('div', { class: 'col grow', style: 'min-width:0;gap:1px' }, @@ -1898,6 +1902,21 @@ export function messagesFeature(ctx) { (c.members ? t('msgMembers', { n: c.members }) + ' · ' : '') + c.category + (c.description ? ' · ' + c.description : ''))))); } + function hubPanel({ preview = false } = {}) { + loadHub().catch(() => {}); + return h('div', { class: 'col community-discovery', style: 'gap:8px' }, + h('div', { class: 'row between', style: 'align-items:baseline' }, + h('h3', { style: 'margin:0' }, t(preview ? 'msgPopularCommunities' : 'msgDiscoverTitle')), + preview ? h('button', { class: 'btn-sm', onClick: (e) => { e.stopPropagation(); ui.msgJoinText = ''; ui.msgHomePanel = 'join'; render(); } }, t('msgBrowseCommunities')) : null), + h('a', { class: 'small muted', href: 'https://vectorapp.io/hub/', target: '_blank', rel: 'noopener noreferrer' }, t('msgHubCredit')), + hub.state === 'error' ? h('div', { class: 'row gap6', style: 'align-items:center' }, + h('span', { class: 'small muted grow' }, t('msgHubFailed')), + h('button', { class: 'btn-sm', onClick: (e) => { e.stopPropagation(); hub.state = 'idle'; loadHub().catch(() => {}); } }, t('retry'))) + : hub.state !== 'ready' ? h('div', { class: 'row gap6', style: 'align-items:center;padding:6px 0' }, h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, t('msgHubLoading'))) + : h('div', { class: 'list' + (preview ? '' : ' hub-list') }, hubRows({ preview })), + h('div', { class: 'small muted' }, t('msgWhiteNoiseDiscovery'))); + } + function joinFromText(text) { const parsed = parseInviteLink(text); if (!parsed) { toast(t('msgBadInvite')); return; } @@ -11091,7 +11110,6 @@ export function messagesFeature(ctx) { h('button', { class: 'btn-sm', onClick: () => { ui.msgHomePanel = ui.msgHomePanel === 'create' ? null : 'create'; render(); } }, t('msgCreate'))))); if (pendingLink && pendingLink.where === 'communities') kids.push(linkInviteCard()); if (ui.msgHomePanel === 'join') { - loadHub().catch(() => {}); const isLink = !!parseInviteLink(ui.msgJoinText || ''); kids.push(h('div', { class: 'col', style: 'gap:8px' }, h('div', { class: 'row gap6' }, @@ -11101,13 +11119,7 @@ export function messagesFeature(ctx) { onKeydown: (e) => { if (e.key === 'Enter' && isLink) joinFromText(ui.msgJoinText); }, }), isLink ? h('button', { class: 'btn-sm btn-primary', onClick: () => joinFromText(ui.msgJoinText) }, t('msgJoin')) : null), - isLink ? null : h('div', { class: 'row between', style: 'align-items:baseline' }, - h('span', { class: 'small muted' }, t('msgDiscoverTitle')), - h('a', { class: 'small muted', href: 'https://vectorapp.io/hub/', target: '_blank', rel: 'noopener noreferrer' }, t('msgHubCredit'))), - isLink ? null - : hub.state === 'error' ? h('div', { class: 'small muted' }, t('msgHubFailed')) - : hub.state !== 'ready' ? h('div', { class: 'row gap6', style: 'align-items:center;padding:6px 0' }, h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, t('msgHubLoading'))) - : h('div', { class: 'list hub-list' }, hubRows()))); + isLink ? null : hubPanel())); } if (ui.msgHomePanel === 'create') kids.push(h('div', { class: 'row gap6' }, @@ -11154,6 +11166,8 @@ export function messagesFeature(ctx) { kids.push(h('button', { class: 'linklike small', onClick: () => { ui.msgAllRooms = false; render(); } }, t('msgShowFewerDms'))); + if (ui.msgHomePanel !== 'join') kids.push(hubPanel({ preview: true })); + return h('div', { class: 'card col chat-page', style: 'gap:10px' }, ...kids); } diff --git a/src/i18n.js b/src/i18n.js index c6c2007..8881cfe 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -674,7 +674,11 @@ const STR = { msgCreate: 'Create', msgInvitePlaceholder: 'Paste an invite link, or search', msgDiscoverTitle: 'Suggested communities', - msgHubCredit: 'From the Vector Hub', + msgPopularCommunities: 'Popular communities', + msgBrowseCommunities: 'Browse all', + msgHubCredit: 'Concord · From the Vector Hub', + msgHubNoSuggestions: 'No new communities to suggest right now.', + msgWhiteNoiseDiscovery: 'White Noise groups require an invitation. Ask a member to invite your npub.', msgHubLoading: 'Finding communities…', msgHubFailed: 'Could not load suggestions right now.', msgJoined: 'joined', -- cgit v1.2.3