Merge branch 'dev'

dev
gravel 11 months ago
commit 83d76756a6
Signed by: gravel
GPG Key ID: C0538F3C906B308F

@ -46,10 +46,12 @@
html {
font-size: clamp(12px, 2vw, var(--max-font-size-unitless) * 1px);
height: 100%;
}
body {
margin: 0;
height: 100%;
}
#toggle-theme-switch {
@ -57,8 +59,10 @@ body {
}
#theming-root {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 100%;
margin: 0;
background-color: var(--secondary-color);
color: var(--primary-color);
@ -119,7 +123,7 @@ html.js .noscript, .hidden {
/* box-shadow: 0.05em 0.05em 0.1em 0 #4444;*/
}
#tbl_communities .room-label, #details-modal .room-label {
#tbl_communities .room-label, #details-modal .room-label, .sample-search {
color: black;
}
@ -127,11 +131,11 @@ html.js .noscript, .hidden {
opacity: 0.75;
}
.room-label-user {
.room-label-user, .sample-search-tag {
background-color: greenyellow;
}
.room-label-reserved {
.room-label-reserved, .sample-search-tag-reserved {
background-color: yellow;
}
@ -139,6 +143,14 @@ html.js .noscript, .hidden {
background-color: pink;
}
.sample-search-plain {
background-color: lightgrey;
}
.sample-search-language {
background-color: whitesmoke;
}
#tbl_communities .room-label:not(.room-label-highlighted) {
background-color: transparent;
color: var(--primary-color);
@ -167,6 +179,67 @@ header {
flex-grow: 1;
}
#search-container {
display: flex;
flex-direction: column;
max-height: 10rem;
justify-content: center;
box-sizing: border-box;
overflow: hidden;
transition: max-height 0.5s;
}
#search-container > * {
margin-bottom: 1rem;
}
.collapsed {
max-height: 0 !important;
}
#search-container.collapsed > * {
display: none !important;
}
#search {
display: flex;
justify-content: center;
align-items: baseline;
margin-inline: 1rem;
}
#search-bar {
height: 1.25rem;
color: var(--primary-color);
background-color: var(--secondary-color);
border: 1px var(--primary-color) solid;
text-align: right;
padding: 0.1rem 0.25rem;
}
#search-bar.search-no-results {
color: red;
}
#search-bar:placeholder-shown ~ .btn-clear-search {
visibility: hidden;
}
.btn-clear-search {
margin-right: 1rem;
font-size: 1.5rem;
padding: 0.25rem 1rem;
}
#sample-searches {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-inline: 1rem;
justify-content: center;
row-gap: 0.25rem;
}
/* --- Table --- */
#tbl_communities {
@ -398,6 +471,10 @@ a[href^="https:"] .protocol-indicator::after {
/* --- Footer --- */
#footer-divider {
width: 90%;
}
footer {
display: flex;
flex-direction: column;

@ -22,7 +22,8 @@ export const dom = {
hostname: row.getAttribute(ATTRIBUTES.ROW.HOSTNAME),
public_key: row.getAttribute(ATTRIBUTES.ROW.PUBLIC_KEY),
staff: row.getAttribute(ATTRIBUTES.ROW.STAFF_DATA),
tags: row.getAttribute(ATTRIBUTES.ROW.TAGS),
/** @type {{type: string, text: string, description: string}[]} */
tags: JSON.parse(row.getAttribute(ATTRIBUTES.ROW.TAGS)),
icon: row.getAttribute(ATTRIBUTES.ROW.ROOM_ICON),
has_icon: row.getAttribute(ATTRIBUTES.ROW.ROOM_ICON).trim() != "",
icon_safety: row.getAttribute(ATTRIBUTES.ROW.ROOM_ICON_SAFETY),
@ -41,6 +42,13 @@ export const dom = {
servers_hidden: () => document.getElementById("servers_hidden"),
snackbar: () => document.getElementById("copy-snackbar"),
qr_code_buttons: () => document.querySelectorAll('.qr-code-button'),
/** @return {HTMLInputElement | null} */
btn_toggle_search: () => document.querySelector('#btn-toggle-search'),
/** @return {HTMLInputElement | null} */
search_bar: () => document.querySelector('#search-bar'),
btn_clear_search: () => document.querySelector("#btn-clear-search"),
search_container: () => document.querySelector("#search-container"),
sample_searches: () => document.querySelectorAll(".sample-search")
}
export const JOIN_URL_PASTE = "Copied URL to clipboard. Paste into Session app to join";
@ -89,9 +97,47 @@ export const ATTRIBUTES = {
},
HYDRATION: {
CONTENT: 'data-hydrate-with'
},
SEARCH: {
TARGET_SEARCH: 'data-search'
}
};
export const CLASSES = {
COMPONENTS: {
COLLAPSED: 'collapsed',
},
SEARCH: {
NO_RESULTS: 'search-no-results',
}
}
const CODEPOINT_REGIONAL_INDICATOR_A = 0x1F1E6;
const CODEPOINT_LOWERCASE_A = 0x61;
/**
*
* @param {string} flag
*/
export function flagToLanguageAscii(flag) {
const regionalIndicators = [0, 2].map(idx => flag.codePointAt(idx));
const ascii = regionalIndicators
.map(codePoint => codePoint - CODEPOINT_REGIONAL_INDICATOR_A)
.map(codePoint => codePoint + CODEPOINT_LOWERCASE_A)
.map(codePoint => String.fromCodePoint(codePoint))
.join("");
switch (ascii) {
case "gb":
return "en";
case "cn":
return "zh";
default:
return ascii;
}
}
export function columnAscendingByDefault(column) {
return column != COLUMN.USERS;
}

@ -17,7 +17,7 @@
import {
dom, COLUMN, COLUMN_LITERAL, COMPARISON, ATTRIBUTES,
columnAscendingByDefault, columnIsSortable, COLUMN_TRANSFORMATION,
element, JOIN_URL_PASTE, communityQRCodeURL, STAFF_ID_PASTE, IDENTIFIER_PASTE, DETAILS_LINK_PASTE
element, JOIN_URL_PASTE, communityQRCodeURL, STAFF_ID_PASTE, IDENTIFIER_PASTE, DETAILS_LINK_PASTE, CLASSES, flagToLanguageAscii
} from './js/constants.js';
// Hidden communities for transparency.
@ -129,16 +129,26 @@ function onLoad() {
hideBadCommunities();
// Sort by server to show off new feature & align colors.
sortTable(COLUMN.SERVER_ICON);
initializeSearch();
createJoinLinkButtons();
markSortableColumns();
addQRModalHandlers();
addServerIconInteractions();
addSearchInteractions();
preloadImages();
setInterval(() => {
preloadImages();
}, 60 * 60E3);
reactToURLParameters();
addInformativeInteractions();
Array.from(document.querySelectorAll('.enter-clicks')).forEach(element => {
// @ts-ignore
element.addEventListener('keydown', (/** @type {KeyboardEvent} */ ev) => {
if (ev.key == "Enter") {
ev.currentTarget.click();
}
})
})
}
/**
@ -200,7 +210,7 @@ function displayQRModal(communityID, pane = 0) {
tagContainer.innerHTML = "";
tagContainer.append(
...JSON.parse(rowInfo.tags).map(tag => tagBody(tag))
...rowInfo.tags.map(tag => tagBody(tag))
);
dom.details_modal_qr_code().src = communityQRCodeURL(communityID);
@ -438,6 +448,46 @@ function addServerIconInteractions() {
}
}
function addSearchInteractions() {
dom.btn_toggle_search()?.addEventListener('click', function (ev) {
location.hash="#";
const container = dom.search_container();
container?.classList.toggle(CLASSES.COMPONENTS.COLLAPSED);
if (!container?.classList.contains(CLASSES.COMPONENTS.COLLAPSED)) {
const searchBar = dom.search_bar();
searchBar?.focus();
// Inconsistent; attempt to align search bar to top to make more space for results.
searchBar?.scrollIntoView({ behavior: 'smooth', inline: 'start' });
} else {
useSearchTerm("");
}
})
dom.search_bar()?.addEventListener('keydown', function () {
setTimeout(() => useSearchTerm(this.value), 0);
})
dom.search_bar()?.addEventListener('keyup', function (ev) {
if (ev.key === "Enter") {
this.blur();
}
useSearchTerm(this.value);
})
dom.btn_clear_search()?.addEventListener('click', function () {
useSearchTerm("");
const searchBar = dom.search_bar();
searchBar?.focus();
searchBar.value = "";
})
Array.from(dom.sample_searches()).forEach(button => button.addEventListener('click', function() {
const targetSearch = button.getAttribute(ATTRIBUTES.SEARCH.TARGET_SEARCH);
useSearchTerm(targetSearch);
dom.search_bar().value = targetSearch;
}))
}
/**
* Function comparing two elements.
*
@ -562,25 +612,84 @@ function markSortableColumns() {
};
}
/**
* @type {HTMLTableRowElement[]}
*/
const communityFullRowCache = [];
function initializeSearch() {
communityFullRowCache.push(...dom.tbl_communities_content_rows());
}
/**
*
* @param {string} [rawTerm]
*/
function useSearchTerm(rawTerm) {
if (!rawTerm) {
replaceRowsWith(communityFullRowCache);
dom.search_bar()?.classList.remove(CLASSES.SEARCH.NO_RESULTS);
} else {
const term = rawTerm.toLowerCase().replace(/lang:(\S+)/g, "").trim();
const termTags = Array.from(rawTerm.matchAll(/#[^#\s]+/g)).map(match => match[0].slice(1).toLowerCase());
const termLanguage = rawTerm.match(/lang:(\S+)/)?.[1];
const newRows = communityFullRowCache.filter(
row => {
const rowInfo = dom.row_info(row);
const langAscii = rowInfo.language_flag && flagToLanguageAscii(rowInfo.language_flag).toLowerCase();
const rowName = rowInfo.name.toLowerCase();
const rowDesc = rowInfo.description.toLowerCase();
const rowTags = rowInfo.tags.map(({text}) => text.replace(/\s+/g, "-"));
if (termLanguage && !langAscii.includes(termLanguage.toLowerCase())) {
return false;
}
if (termTags.length >= 1) {
if (termTags.some(tag => rowTags.some(rowTag => rowTag.includes(tag)))) {
return true;
}
}
return rowName.includes(term) || rowDesc.includes(term);
}
);
if (newRows.length === 0) {
dom.search_bar()?.classList.add(CLASSES.SEARCH.NO_RESULTS);
} else {
dom.search_bar()?.classList.remove(CLASSES.SEARCH.NO_RESULTS);
}
replaceRowsWith(newRows);
}
sortTable();
}
function replaceRowsWith(rows) {
dom.tbl_communities_content_rows().forEach(row => row.remove());
dom.tbl_communities().querySelector("tbody").append(...rows);
}
/**
* Sorts the default communities table according the given column.
* Sort direction is determined by defaults; successive sorts
* on the same column reverse the sort direction.
* @param {number} column - Numeric ID of column being sorted.
* @param {number} [column] - Numeric ID of column being sorted. Re-applies last sort if absent.
*/
function sortTable(column) {
const table = dom.tbl_communities();
const sortState = getSortState(table);
const sortingAsBefore = column === undefined;
const sortingNewColumn = column !== sortState?.column;
const ascending = sortingNewColumn
? columnAscendingByDefault(column)
: !sortState.ascending;
const compare = makeRowComparer(column, ascending);
const sortedColumn = column ?? sortState?.column;
const ascending =
sortingAsBefore ?
sortState.ascending : (
sortingNewColumn
? columnAscendingByDefault(column)
: !sortState.ascending
);
const compare = makeRowComparer(sortedColumn, ascending);
const rows = Array.from(table.rows).slice(1);
rows.sort(compare);
rows.forEach((row) => row.remove());
table.querySelector("tbody").append(...rows);
setSortState(table, { ascending, column });
replaceRowsWith(rows);
setSortState(table, { ascending, column: sortedColumn });
}
// `html.js` selector for styling purposes

@ -0,0 +1,44 @@
<?php
$sample_searches_tags_reserved = ["new", "we're here"];
$sample_searches_plain = ["language", "Australia"];
$sample_searches_tags = ["official", "privacy", "android", "crypto"];
$sample_searches_languages = [["en", "🇬🇧"], ["zh", "🇨🇳"]];
$sample_searches = [];
foreach ($sample_searches_tags_reserved as $tag) {
$search = str_replace(" ", "-", $tag);
$sample_searches[] = array("type" => "tag-reserved", "text" => "#$tag", "search" => "#$search");
}
foreach ($sample_searches_tags as $tag) {
$search = str_replace(" ", "-", $tag);
$sample_searches[] = array("type" => "tag", "text" => "#$tag", "search" => "#$search");
}
foreach ($sample_searches_plain as $text) {
$sample_searches[] = array("type" => "plain", "text" => $text, "search" => $text);
}
foreach ($sample_searches_languages as $language_array) {
$sample_searches[] = array("type" => "language", "text" => $language_array[1], "search" => "lang:" . $language_array[0]);
}
?>
<div id="search-container" class="collapsed">
<div id="search">
<?php // Phantom element for alignnment ?>
<span class="btn-clear-search" style="visibility: hidden;" tabindex="-1">×</span>
<input id="search-bar" autocomplete="off" type="text" placeholder="Search for Communities">
<span id="btn-clear-search" class="btn-clear-search anchorstyle clickable enter-clicks" tabindex="0" title="Clear search">×</span>
</div>
<div id="sample-searches">
<?php foreach ($sample_searches as $search): ?>
<span
class="badge clickable enter-clicks sample-search sample-search-<?=$search['type']?>"
title="Try searching for <?=$search['search']?>"
data-search="<?=$search['search']?>"
tabindex=0
><?=
$search['text']
?></span>
<?php endforeach; ?>
</div>
</div>

@ -72,10 +72,16 @@
<div id="header-start"></div>
<div id="header-end">
<a
id="link-about"
href="#footer"
title="Learn more about sessioncommunities.online."
id="link-about"
href="#footer"
title="Learn more about sessioncommunities.online."
>About</a>
<span
id="btn-toggle-search"
class="anchorstyle clickable enter-clicks js-only"
title="Open search bar."
tabindex="0"
>Search</span>
<a
id="link-more-sites"
href="#more-sites"
@ -83,7 +89,7 @@
>More groups</a>
<label
for="toggle-theme-switch"
class="anchorstyle clickable"
class="anchorstyle clickable enter-clicks"
title="Switch color theme"
tabindex="0"
>Switch theme</label>
@ -97,11 +103,16 @@
</div>
</header>
<h1 id="headline">Session Communities</h1>
<?php include "+components/communities-search.php" ?>
<?php include "+components/qr-modals.php" ?>
<?php include "+components/tbl-communities.php" ?>
<hr>
<gap></gap>
<hr id="footer-divider">
<footer id="footer">
<p id="server_summary">
@ -200,9 +211,10 @@
>oxen.directory</a
><span
id="more-sites-info-button"
class="clickable"
class="clickable enter-clicks"
href="#"
title="Read more about sites hosting additional groups."
tabindex="0"
>()</span>
</nav>
<nav id="about-session" class="highlight">

Loading…
Cancel
Save