mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-26 01:02:24 +00:00
directory: show only recent groups in active/new tabs, page improvements (#6290)
* directory: show only recent groups as active or new * round times * sorting order * fix links * improve
This commit is contained in:
@@ -23,13 +23,15 @@ import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.ByteString.Base64.URL as B64URL
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.ByteString.Lazy as LB
|
||||
import Data.Int (Int64)
|
||||
import Data.List (isPrefixOf)
|
||||
import Data.Maybe (catMaybes, fromMaybe)
|
||||
import qualified Data.Set as S
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Data.Time.Clock (UTCTime, getCurrentTime)
|
||||
import Data.Time.Clock
|
||||
import Data.Time.Clock.System
|
||||
import Data.Time.Format.ISO8601 (iso8601Show)
|
||||
import Directory.Store
|
||||
import Simplex.Chat.Markdown
|
||||
@@ -66,8 +68,8 @@ data DirectoryEntry = DirectoryEntry
|
||||
shortDescr :: Maybe MarkdownList,
|
||||
welcomeMessage :: Maybe MarkdownList,
|
||||
imageFile :: Maybe String,
|
||||
activeAt :: UTCTime,
|
||||
createdAt :: UTCTime
|
||||
activeAt :: Maybe UTCTime,
|
||||
createdAt :: Maybe UTCTime
|
||||
}
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''DirectoryEntry)
|
||||
@@ -78,8 +80,18 @@ $(JQ.deriveJSON defaultJSON ''DirectoryListing)
|
||||
|
||||
type ImageFileData = ByteString
|
||||
|
||||
groupDirectoryEntry :: GroupInfoSummary -> Maybe (DirectoryEntry, Maybe (FilePath, ImageFileData))
|
||||
groupDirectoryEntry (GIS GroupInfo {groupProfile, chatTs, createdAt} summary gLink_) =
|
||||
newOrActive :: NominalDiffTime
|
||||
newOrActive = 30 * nominalDay
|
||||
|
||||
recentRoundedTime :: Int64 -> UTCTime -> UTCTime -> Maybe UTCTime
|
||||
recentRoundedTime roundTo now t
|
||||
| diffUTCTime now t > newOrActive = Nothing
|
||||
| otherwise =
|
||||
let secs = (systemSeconds (utcToSystemTime t) `div` roundTo) * roundTo
|
||||
in Just $ systemToUTCTime $ MkSystemTime secs 0
|
||||
|
||||
groupDirectoryEntry :: UTCTime -> GroupInfoSummary -> Maybe (DirectoryEntry, Maybe (FilePath, ImageFileData))
|
||||
groupDirectoryEntry now (GIS GroupInfo {groupProfile, chatTs, createdAt} summary gLink_) =
|
||||
let GroupProfile {displayName, shortDescr, description, image, memberAdmission} = groupProfile
|
||||
entryType = DETGroup memberAdmission summary
|
||||
entry groupLink =
|
||||
@@ -91,8 +103,8 @@ groupDirectoryEntry (GIS GroupInfo {groupProfile, chatTs, createdAt} summary gLi
|
||||
shortDescr = toFormattedText <$> shortDescr,
|
||||
welcomeMessage = toFormattedText <$> description,
|
||||
imageFile = fst <$> imgData,
|
||||
activeAt = fromMaybe createdAt chatTs,
|
||||
createdAt
|
||||
activeAt = recentRoundedTime 900 now $ fromMaybe createdAt chatTs,
|
||||
createdAt = recentRoundedTime 86400 now createdAt
|
||||
}
|
||||
imgData = imgFileData groupLink =<< image
|
||||
in (de, imgData)
|
||||
@@ -121,7 +133,7 @@ generateListing st dir gs = do
|
||||
createDirectoryIfMissing True (newDir </> listingImageFolder)
|
||||
gs'' <-
|
||||
fmap catMaybes $ forM gs' $ \g@(GIS GroupInfo {groupId} _ _) ->
|
||||
forM (groupDirectoryEntry g) $ \(g', img) -> do
|
||||
forM (groupDirectoryEntry ts g) $ \(g', img) -> do
|
||||
forM_ img $ \(imgFile, imgData) -> B.writeFile (newDir </> imgFile) imgData
|
||||
pure (groupId, g')
|
||||
saveListing newDir listingFileName gs''
|
||||
|
||||
@@ -263,7 +263,7 @@ templateEngineOverride: njk
|
||||
<div id="top-pagination" class="pagination">
|
||||
<button class="text-btn live">Active</button>
|
||||
<button class="text-btn new">New</button>
|
||||
<button class="text-btn top">Top</button>
|
||||
<button class="text-btn top">All</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="directory" style="height: 3000px;"></div>
|
||||
|
||||
@@ -5,12 +5,14 @@ const directoryDataURL = 'https://directory.simplex.chat/data/';
|
||||
|
||||
let allEntries = [];
|
||||
|
||||
let sortedEntries = [];
|
||||
|
||||
let filteredEntries = [];
|
||||
|
||||
let currentSortMode = '';
|
||||
|
||||
let currentSearch = '';
|
||||
|
||||
let currentPage = 1;
|
||||
|
||||
async function initDirectory() {
|
||||
const listing = await fetchJSON(directoryDataURL + 'listing.json')
|
||||
const liveBtn = document.querySelector('#top-pagination .live');
|
||||
@@ -18,24 +20,29 @@ async function initDirectory() {
|
||||
const topBtn = document.querySelector('#top-pagination .top');
|
||||
const searchInput = document.getElementById('search');
|
||||
allEntries = listing.entries
|
||||
renderSortedEntries('top', byMemberCountDesc, topBtn)
|
||||
window.addEventListener('hashchange', renderDirectoryPage);
|
||||
searchInput.addEventListener('input', (e) => renderFilteredEntries(e.target.value));
|
||||
renderEntries('top', bySortPriority, topBtn)
|
||||
searchInput.addEventListener('input', (e) => renderEntries('top', bySortPriority, topBtn, e.target.value.trim()));
|
||||
liveBtn.addEventListener('click', () => renderEntries('live', byActiveAtDesc, liveBtn, ''));
|
||||
newBtn.addEventListener('click', () => renderEntries('new', byCreatedAtDesc, newBtn, ''));
|
||||
topBtn.addEventListener('click', () => renderEntries('top', bySortPriority, topBtn, ''));
|
||||
|
||||
liveBtn.addEventListener('click', () => renderSortedEntries('live', byActiveAtDesc, liveBtn));
|
||||
newBtn.addEventListener('click', () => renderSortedEntries('new', byCreatedAtDesc, newBtn));
|
||||
topBtn.addEventListener('click', () => renderSortedEntries('top', byMemberCountDesc, topBtn));
|
||||
|
||||
function renderSortedEntries(mode, comparator, btn) {
|
||||
if (currentSortMode === mode) return;
|
||||
function renderEntries(mode, comparator, btn, search) {
|
||||
if (currentSortMode === mode && search == currentSearch) return;
|
||||
currentSortMode = mode;
|
||||
if (location.hash) location.hash = '';
|
||||
liveBtn.classList.remove('active');
|
||||
newBtn.classList.remove('active');
|
||||
topBtn.classList.remove('active');
|
||||
btn.classList.add('active');
|
||||
sortedEntries = allEntries.slice().sort(comparator);
|
||||
renderFilteredEntries(searchInput.value);
|
||||
if (search == '') {
|
||||
currentSearch = '';
|
||||
currentPage = 1;
|
||||
searchInput.value = '';
|
||||
btn.classList.add('active');
|
||||
} else {
|
||||
currentSearch = search;
|
||||
}
|
||||
filteredEntries = filterEntries(mode, search ?? '').sort(comparator);
|
||||
renderDirectoryPage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,18 +51,20 @@ function renderDirectoryPage() {
|
||||
displayEntries(currentEntries);
|
||||
}
|
||||
|
||||
function renderFilteredEntries(s) {
|
||||
const query = s.toLowerCase().trim();
|
||||
if (query === '') {
|
||||
filteredEntries = sortedEntries.slice();
|
||||
} else {
|
||||
filteredEntries = sortedEntries.filter(entry =>
|
||||
(entry.displayName || '').toLowerCase().includes(query)
|
||||
|| includesQuery(entry.shortDescr, query)
|
||||
|| includesQuery(entry.welcomeMessage, query)
|
||||
);
|
||||
}
|
||||
renderDirectoryPage();
|
||||
function filterEntries(mode, s) {
|
||||
if (s === '' && mode == 'top') return allEntries.slice();
|
||||
const query = s.toLowerCase();
|
||||
return allEntries.filter(entry =>
|
||||
( mode === 'top'
|
||||
|| (mode === 'new' && entry.createdAt)
|
||||
|| (mode === 'live' && entry.activeAt)
|
||||
) &&
|
||||
( query === ''
|
||||
|| (entry.displayName || '').toLowerCase().includes(query)
|
||||
|| includesQuery(entry.shortDescr, query)
|
||||
|| includesQuery(entry.welcomeMessage, query)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function includesQuery(field, query) {
|
||||
@@ -91,18 +100,18 @@ async function fetchJSON(url) {
|
||||
}
|
||||
}
|
||||
|
||||
function byMemberCountDesc(entry1, entry2) {
|
||||
return entryMemberCount(entry2) - entryMemberCount(entry1);
|
||||
function bySortPriority(entry1, entry2) {
|
||||
return entrySortPriority(entry2) - entrySortPriority(entry1);
|
||||
}
|
||||
|
||||
function byActiveAtDesc(entry1, entry2) {
|
||||
return (roundedTs(entry2.activeAt) - roundedTs(entry1.activeAt)) * 10
|
||||
+ Math.sign(byMemberCountDesc(entry1, entry2));
|
||||
+ Math.sign(bySortPriority(entry1, entry2));
|
||||
}
|
||||
|
||||
function byCreatedAtDesc(entry1, entry2) {
|
||||
return (roundedTs(entry2.createdAt) - roundedTs(entry1.createdAt)) * 10
|
||||
+ Math.sign(byMemberCountDesc(entry1, entry2));
|
||||
+ Math.sign(bySortPriority(entry1, entry2));
|
||||
}
|
||||
|
||||
function roundedTs(s) {
|
||||
@@ -114,6 +123,14 @@ function roundedTs(s) {
|
||||
}
|
||||
}
|
||||
|
||||
const simplexUsersGroup = 'SimpleX users group';
|
||||
|
||||
function entrySortPriority(entry) {
|
||||
return entry.displayName === simplexUsersGroup
|
||||
? Number.MAX_VALUE
|
||||
: entryMemberCount(entry)
|
||||
}
|
||||
|
||||
function entryMemberCount(entry) {
|
||||
return entry.entryType.type == 'group'
|
||||
? (entry.entryType.summary?.currentMembers ?? 0)
|
||||
@@ -204,7 +221,7 @@ function displayEntries(entries) {
|
||||
console.log(e);
|
||||
imgElement.href = groupLinkUri;
|
||||
}
|
||||
if (!isCurrentSite(imgElement.href)) imgElement.target = "_blank";
|
||||
imgElement.target = "_blank";
|
||||
imgElement.title = `Join ${displayName}`;
|
||||
entryDiv.appendChild(imgElement);
|
||||
|
||||
@@ -223,13 +240,13 @@ function displayEntries(entries) {
|
||||
}
|
||||
|
||||
function goToPage(p) {
|
||||
location.hash = p.toString();
|
||||
currentPage = p;
|
||||
renderDirectoryPage();
|
||||
}
|
||||
|
||||
function addPagination(entries) {
|
||||
const entriesPerPage = 10;
|
||||
const totalPages = Math.ceil(entries.length / entriesPerPage);
|
||||
let currentPage = parseInt(location.hash.slice(1)) || 1;
|
||||
if (currentPage < 1) currentPage = 1;
|
||||
if (currentPage > totalPages) currentPage = totalPages;
|
||||
|
||||
@@ -376,7 +393,7 @@ function platformSimplexUri(uri) {
|
||||
if (remainingParams) newFragment += '?' + remainingParams;
|
||||
return `https://${host}:/${linkType}#${newFragment}`;
|
||||
} else {
|
||||
return `https://simplex.chat/${fragment}`;
|
||||
return `https://simplex.chat/${linkType}#${fragment}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user