diff --git a/apps/simplex-directory-service/src/Directory/Listing.hs b/apps/simplex-directory-service/src/Directory/Listing.hs
index b9b50cb87b..cef478c273 100644
--- a/apps/simplex-directory-service/src/Directory/Listing.hs
+++ b/apps/simplex-directory-service/src/Directory/Listing.hs
@@ -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''
diff --git a/website/src/directory.html b/website/src/directory.html
index 333d7faed8..1d87ffdaa9 100644
--- a/website/src/directory.html
+++ b/website/src/directory.html
@@ -263,7 +263,7 @@ templateEngineOverride: njk
diff --git a/website/src/js/directory.js b/website/src/js/directory.js
index 313bdeedec..72c93d35fb 100644
--- a/website/src/js/directory.js
+++ b/website/src/js/directory.js
@@ -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}`;
}
}