Merge branch 'main' into table-improvements

dev
gravel 3 years ago
commit 6a908d308b
Signed by: gravel
GPG Key ID: C0538F3C906B308F

@ -1 +1 @@
Subproject commit b544b6680e834d4359294ed075393de61d3dce8d Subproject commit f2aaed46ea2ef3a6ff03b6eee6f70c81cc20f069

@ -3,7 +3,11 @@
// change in the foreseeable future. // change in the foreseeable future.
export const dom = { export const dom = {
/** @return {HTMLTableElement | null} */
tbl_communities: () => document.getElementById("tbl_communities"), tbl_communities: () => document.getElementById("tbl_communities"),
tbl_communities_content_rows:
() => Array.from(dom.tbl_communities()?.rows)?.filter(row => !row.querySelector('th')),
meta_timestamp: () => document.querySelector('meta[name=timestamp]'),
last_checked: () => document.getElementById("last_checked_value"), last_checked: () => document.getElementById("last_checked_value"),
qr_modal: (communityID) => document.getElementById(`modal_${communityID}`), qr_modal: (communityID) => document.getElementById(`modal_${communityID}`),
join_urls: () => document.getElementsByClassName("join_url_container"), join_urls: () => document.getElementsByClassName("join_url_container"),
@ -56,7 +60,7 @@ export function columnIsSortable(column) {
const TRANSFORMATION = { const TRANSFORMATION = {
numeric: (el) => parseInt(el.innerText), numeric: (el) => parseInt(el.innerText),
casefold: (el) => el.innerText.toLowerCase().trim(), casefold: (el) => el.innerText.toLowerCase().trim(),
getPubkey: (el) => el.getAttribute("data-pubkey") getSortKey: (el) => el.getAttribute("data-sort-by")
} }
/** /**
@ -67,7 +71,7 @@ export const COLUMN_TRANSFORMATION = {
[COLUMN.IDENTIFIER]: TRANSFORMATION.casefold, [COLUMN.IDENTIFIER]: TRANSFORMATION.casefold,
[COLUMN.NAME]: TRANSFORMATION.casefold, [COLUMN.NAME]: TRANSFORMATION.casefold,
[COLUMN.DESCRIPTION]: TRANSFORMATION.casefold, [COLUMN.DESCRIPTION]: TRANSFORMATION.casefold,
[COLUMN.SERVER_ICON]: TRANSFORMATION.getPubkey [COLUMN.SERVER_ICON]: TRANSFORMATION.getSortKey
} }
/** /**

@ -22,32 +22,18 @@ import {
// Hidden communities for transparency. // Hidden communities for transparency.
const filteredCommunities = { const filteredCommunities = {
tests: [ tests: [
"2e9345+c7fb", // TestRoom
"762ba9+c7fb", // TesterRoom
"b4d829+c7fb", // Test
"e5853a+c7fb", // testtest
"fishing+8e2e", // Example group from PySOGS documentation "fishing+8e2e", // Example group from PySOGS documentation
"test+118d", // Testing 1, 2, 3 "test+118d", // Testing 1, 2, 3
"test+13f6", // Testing room "test+13f6", // Testing room2
"test+c01b", // Testing room
"test+fe93", // 测试Test) "test+fe93", // 测试Test)
"xyz+7908", // XYZ Room "xyz+7908", // XYZ Room
"xyz+efca", // XYZ Room
], ],
offensive: [ offensive: [
"60fa60+c7fb", // "N-word" Community "AlexMed+e093", // drug trading?
"ab1a4d+c7fb", // zUnsensored Group (CSAM)
"AlexMed+e093", //
"gore+e5e0", // gore "gore+e5e0", // gore
"RU-STEROID+e093" // "RU-STEROID+e093" // drug trading?
], ],
// These communities should be checked regularly
// in case they update their PySOGS version
legacy: [
"Ukraine+02bd" // https://reccacon.com/view/room/Ukraine
]
}; };
// This can be achieved with `text-overflow: ellipsis` instead // This can be achieved with `text-overflow: ellipsis` instead
@ -60,13 +46,27 @@ const transformJoinURL = (join_link) => {
}); });
} }
function onLoad(timestamp) { function getTimestamp() {
setLastChecked(timestamp); const timestampRaw = dom.meta_timestamp()
?.getAttribute('content');
if (!timestampRaw) return null;
const timestamp = parseInt(timestampRaw);
if (Number.isNaN(timestamp)) return null;
return timestamp;
}
function onLoad() {
const timestamp = getTimestamp();
if (timestamp !== null) {
setLastChecked(timestamp);
}
hideBadCommunities(); hideBadCommunities();
// Sort by server to show off new feature & align colors. // Sort by server to show off new feature & align colors.
sortTable(COLUMN.SERVER_ICON); sortTable(COLUMN.SERVER_ICON);
createJoinLinkButtons(); createJoinLinkButtons();
markSortableColumns(); markSortableColumns();
addQRModalHandlers();
addServerIconInteractions();
} }
function displayQRModal(communityID) { function displayQRModal(communityID) {
@ -77,6 +77,24 @@ function hideQRModal(communityID) {
dom.qr_modal(communityID).style.display = "none"; dom.qr_modal(communityID).style.display = "none";
} }
function addQRModalHandlers() {
const rows = dom.tbl_communities_content_rows();
if (!rows) throw new Error("Rows not found");
for (const row of rows) {
const communityID = row.getAttribute('data-identifier');
row.querySelector('.td_qr_code').addEventListener(
'click',
() => displayQRModal(communityID)
);
const closeButton =
dom.qr_modal(communityID).querySelector('.qr-code-modal-close');
closeButton.addEventListener(
'click',
() => hideQRModal(communityID)
);
}
}
function createJoinLinkButtons() { function createJoinLinkButtons() {
const join_URLs = dom.join_urls(); const join_URLs = dom.join_urls();
Array.from(join_URLs).forEach((td_url) => { Array.from(join_URLs).forEach((td_url) => {
@ -89,7 +107,7 @@ function createJoinLinkButtons() {
function hideBadCommunities() { function hideBadCommunities() {
let numberOfHiddenCommunities = 0; let numberOfHiddenCommunities = 0;
for (const category of ['tests', 'offensive', 'legacy']) { for (const category of ['tests', 'offensive']) {
numberOfHiddenCommunities += numberOfHiddenCommunities +=
filteredCommunities[category] filteredCommunities[category]
.map(hideElementByID) .map(hideElementByID)
@ -138,6 +156,19 @@ function setLastChecked(last_checked) {
timestamp_element.innerText = `${time_passed_in_minutes} minutes ago`; timestamp_element.innerText = `${time_passed_in_minutes} minutes ago`;
} }
// TODO: Move info into dynamic modal.
function addServerIconInteractions() {
const rows = dom.tbl_communities_content_rows();
for (const row of rows) {
const hostname = row.getAttribute('data-hostname');
const publicKey = row.getAttribute('data-pubkey');
const serverIcon = row.querySelector('.td_server_icon');
serverIcon.addEventListener('click', () => {
alert(`Host: ${hostname}?>\n\nPublic key:\n${publicKey}?>`);
});
}
}
/** /**
* Function comparing two elements. * Function comparing two elements.
* *
@ -253,9 +284,13 @@ function markSortableColumns() {
const table = dom.tbl_communities(); const table = dom.tbl_communities();
const header_cells = table.querySelectorAll('th'); const header_cells = table.querySelectorAll('th');
for (let colno = 0; colno < header_cells.length; colno++) { for (let colno = 0; colno < header_cells.length; colno++) {
if (!columnIsSortable(colno)) continue; if (!columnIsSortable(colno)) continue;
header_cells[colno].classList.add('sortable'); header_cells[colno].classList.add('sortable');
} header_cells[colno].addEventListener(
'click',
() => sortTable(colno)
)
};
} }
/** /**
@ -279,13 +314,7 @@ function sortTable(column) {
setSortState(table, { ascending, column }); setSortState(table, { ascending, column });
} }
// html.js for styling purposes // `html.js` selector for styling purposes
window.document.documentElement.classList.add("js"); document.documentElement.classList.add("js");
// Crude way to export from module script due to inline event handlers.
// Ideally, all handlers would be attached from JS via addEventListener.
Object.assign(window, {
onLoad, sortTable, displayQRModal,
hideQRModal, copyToClipboard
});
document.addEventListener('DOMContentLoaded', () => onLoad());

@ -1,8 +1,4 @@
:root { :root {
/*--session-classic-dark-green: #31f196;*/
/*--session-classic-dark-gray-one: #414141;*/
/*--session-classic-dark-gray-two: #2d2d2d;*/
/*--session-classic-dark-gray-three: #1b1b1b;*/
--alternate-row-color: #e8e8e8; --alternate-row-color: #e8e8e8;
} }
@ -92,8 +88,8 @@ header {
font-size: 1em; font-size: 1em;
} }
#toggle-show-room-ids:not(:checked) #toggle-show-room-ids:not(:checked)
~ #tbl_communities :is(#th_identifier, .td_identifier) { ~ #tbl_communities :is(#th_identifier, .td_identifier) {
display: none; display: none;
} }
@ -142,7 +138,6 @@ header {
-webkit-line-clamp: 3; -webkit-line-clamp: 3;
} }
.td_description::after { .td_description::after {
/* Cover overflowing description lines with internal border. */ /* Cover overflowing description lines with internal border. */
position: absolute; position: absolute;
@ -154,7 +149,7 @@ header {
background-color: var(--row-color); background-color: var(--row-color);
} }
.td_users { .td_users {
text-align: center; text-align: center;
/* font-weight: bold; */ /* font-weight: bold; */
} }
@ -185,7 +180,7 @@ a[href^="https:"] .protocol-indicator::after {
content: "HTTPS"; content: "HTTPS";
} }
.td_qr_code { .td_qr_code {
padding: var(--cell-padding-small) !important; padding: var(--cell-padding-small) !important;
} }
@ -237,7 +232,7 @@ a[href^="https:"] .protocol-indicator::after {
display: none; display: none;
} }
#th_preview, .td_preview { #th_preview, .td_preview {
display: none; display: none;
} }
} }

@ -1,3 +1,11 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/svg+xml" href="favicon.svg" sizes="any"> <link rel="icon" type="image/svg+xml" href="favicon.svg" sizes="any">
<meta name="keywords" content="Session, Community, Anonymous, Chat">
<meta
http-equiv="Content-Security-Policy"
content="
script-src 'self'; img-src 'self' data:; connect-src 'self'; font-src 'none';
object-src 'none'; media-src 'none'; form-action 'none'; base-uri 'self';
"
>

@ -34,7 +34,7 @@
<?php foreach ($rooms as $id => $room): ?> <?php foreach ($rooms as $id => $room): ?>
<div id="modal_<?=$id?>" class="qr-code-modal"> <div id="modal_<?=$id?>" class="qr-code-modal">
<div class="qr-code-modal-content"> <div class="qr-code-modal-content">
<span class="qr-code-modal-close" onclick='hideQRModal("<?=$id?>")'> <span class="qr-code-modal-close">
&times; &times;
</span> </span>
<img <img

@ -6,14 +6,15 @@
// Join URL contents are not guaranteed to have visible text. // Join URL contents are not guaranteed to have visible text.
return $id != "qr" && $id != "preview" && $id != "join_url"; return $id != "qr" && $id != "preview" && $id != "join_url";
} }
function sort_onclick($colno) { function sort_onclick($colno) {
global $TABLE_COLUMNS; global $TABLE_COLUMNS;
$column = $TABLE_COLUMNS[$colno]; $column = $TABLE_COLUMNS[$colno];
if (!column_sortable($column['id'])) return ""; if (!column_sortable($column['id'])) return "";
return " onclick='sortTable($colno)' title='Click to sort this column'"; $name = $column['name'];
return " title='Click to sort by $name'";
} }
$TABLE_COLUMNS = [ $TABLE_COLUMNS = [
['id' => "identifier", 'name' => "Identifier"], ['id' => "identifier", 'name' => "Identifier"],
['id' => "language", 'name' => "L"], ['id' => "language", 'name' => "L"],
@ -51,48 +52,62 @@
$hostname = explode("//", $room->join_link)[1]; $hostname = explode("//", $room->join_link)[1];
$hostname = explode("/", $hostname)[0]; $hostname = explode("/", $hostname)[0];
// Escape external input.
// Ternaries prevent passing null-equal strings, which produce warnings.
$id = htmlspecialchars($id);
$language = $room->language ? htmlspecialchars($room->language) : "";
$name = htmlspecialchars($room->name);
$desc = $room->description ? htmlspecialchars($room->description) : "";
$users = htmlspecialchars($room->active_users);
$preview_link = htmlspecialchars($room->preview_link);
$join_link = htmlspecialchars($room->join_link);
// TODO: Do not forget to rename this escape when merging!
$token = htmlspecialchars($token);
$hostname = htmlspecialchars($hostname);
?> ?>
<tr id="<?=$id?>"> <tr id="<?=$id?>" itemscope itemtype="https://schema.org/EntryPoint"
<td class="td_identifier"><?=$id?></td> data-identifier="<?=$id?>"
<td class="td_language"><?=$room->language?></td> data-pubkey="<?=$pubkey?>"
<td class="td_name" title="'<?=$room->name?>' preview"> data-hostname="<?=$hostname?>"
>
<td class="td_identifier" itemprop="identifier"><?=$id?></td>
<td class="td_language"><?=$language?></td>
<td class="td_name" title="'<?=$room->name?>' preview" itemprop="name">
<a href="<?=$room->preview_link?>" target="_blank" rel="noopener noreferrer"> <a href="<?=$room->preview_link?>" target="_blank" rel="noopener noreferrer">
<?=$room->name?> <?=$room->name?>
</a> </a>
</td> </td>
<td class="td_description" <td class="td_description" itemprop="description"><?=$desc?></td>
><?=$room->description?></td> <td class="td_users"><?=$users?></td>
<td class="td_users"><?=$room->active_users?></td> <td class="td_preview" itemprop="url">
<td class="td_preview"> <a href="<?=$preview_link?>" target="_blank" rel="noopener noreferrer nofollow">
<a href="<?=$room->preview_link?>" target="_blank" rel="noopener noreferrer">
<span class="protocol-indicator"></span> <span class="protocol-indicator"></span>
</a> </a>
</td> </td>
<td class="td_qr_code"> <td class="td_qr_code">
<img <img
class="qr-code-icon" class="qr-code-icon"
src="qrcode-solid.svg" src="qrcode-solid.svg"
onclick='displayQRModal("<?=$id?>")'
alt="Pictogram of a QR code" alt="Pictogram of a QR code"
> >
</td> </td>
<td class="td_server_icon" <td class="td_server_icon"
data-pubkey="<?=$pubkey?>" data-sort-by="<?=$pubkey?>"
title="<?=$hostname?> (<?=$pubkey?>)" title="<?=$hostname?> (<?=$token?>)"
<?php /* Mobile UX. This is not ideal. Move info once implemented. */ ?> item="image"
onclick="alert('Host: <?=$hostname?>\n\nPublic key:\n<?=$pubkey?>')"
> >
<div class="td_server_icon-circle" style="background-color: <?=$icon_color?>"> <div class="td_server_icon-circle" style="background-color: <?=$icon_color?>">
<span><?=strtoupper($pubkey[0] . $pubkey[1])?></span> <span><?=strtoupper($pubkey[0] . $pubkey[1])?></span>
</div> </div>
</td> </td>
<td class="td_join_url"> <td class="td_join_url">
<div class="join_url_container" data-url="<?=$room->join_link?>"> <div class="join_url_container" data-url="<?=$join_link?>">
<a class="join_url show-from-w5" title="<?=$room->join_link?>" <a class="join_url show-from-w5" title="<?=$join_link?>"
><?=$room->join_link?></a> ><?=truncate($join_link, 32)?></a>
<a class="noscript" href="<?=$room->join_link?>" <a class="noscript" href="<?=$join_link?>" rel="external nofollow"
>Copy link</a> >Copy link</a>
</div> </div>
</td> </td>

@ -0,0 +1,12 @@
Mobiililaitteella:
- Napsauta Kopioi-nappia kopioidaksesi ryhmän URL-osoitteen.
- Avaa Session, paina plussan painiketta ja valitse "Liity ryhmään" ("Join Community").
- Liitä kopioitu URL-osoite "Syötä yhteisön URL-osoite"-kenttään, ja paina"Liity".
Mobiililaitteella, kun tämä sivusto on auki tietokoneellasi:
- Paina valitsemasi yhteisön QR-koodipainiketta tietokoneellasi.
- Avaa Session, paina plussan painiketta ja valitse "Liity ryhmään" ("Join Community").
- Valitse ylhäältä "Skannaa QR-koodi" ja skannaa puhelimen kameralla ryhmän QR-koodiin.

@ -0,0 +1,12 @@
На мобильном девайсе:
- Нажмите кнопку «Копировать», чтобы скопировать URL-адрес нужной группы.
- Откройте Session, нажмите на кнопку с плюсом и выберите «Присоединиться к сообществу».
- Вставьте скопированный URL-адрес в поле «Введите URL-адрес сообщества», и нажмите «Присоединиться».
На мобильном девайсе, когда этот сайт открыт на вашем компьютере:
- Нажмите кнопку QR-кода для выбранного сообщества на вашем компьютере.
- Откройте Session, нажмите на кнопку с плюсом и выберите «Присоединиться к сообществу».
- Выберите «Сканировать QR-код» наверху и наведите камеру телефона на QR-код.

@ -13,17 +13,35 @@
<head> <head>
<?php include "+components/page-head.php" ?> <?php include "+components/page-head.php" ?>
<link rel="canonical" href="https://sessioncommunities.online/">
<link rel="stylesheet" href="styles2.css"> <link rel="stylesheet" href="styles2.css">
<script type="module" src="main.js"></script> <script type="module" src="./main.js"></script>
<title>Self-updating list of active Session communities</title> <title>Self-updating list of active Session communities</title>
<meta name="description" content="
Directory of Session Open Groups — public chatrooms within Session Messenger.
Copy these Communities into the Session app
and talk anonymously about Privacy, Security, or Cryptocurrency.
">
<meta name="modified" content="<?=date("Y-m-d H:i:s", $timestamp)?>">
<meta property="og:title" content="Click here for Session Communities">
<meta
property="og:description"
content="<?=count($rooms_assoc)?> Communities and counting — updated every day!"
>
<meta property="og:url" content="https://sessioncommunities.online/">
<meta property="og:type" content="website">
<meta property="og:locale" content="en_US"/>
<meta name="timestamp" content="<?=$timestamp?>">
</head> </head>
<body onload="onLoad(<?php echo $timestamp ?>)"> <body>
<header> <header>
<div id="header-start"></div> <div id="header-start"></div>
<div id="header-end"> <div id="header-end">
<a <a
id="link-instructions" id="link-instructions"
target="_blank" target="_blank"
rel="help"
title="Mutli-language guide to joining communities using the site."
href="instructions.html" href="instructions.html"
>Instructions</a> >Instructions</a>
</div> </div>
@ -50,10 +68,16 @@
</p> </p>
<p id="disclaimer"> <p id="disclaimer">
This site is not affiliated with This site is not affiliated with
<a href="https://optf.ngo">Oxen Privacy Tech Foundation</a>. <a
href="https://optf.ngo"
target="_blank"
>Oxen Privacy Tech Foundation</a>.
<br> <br>
Communities shown are fetched automatically from Communities shown are fetched automatically from
various sources. <a
href="https://github.com/mdPlusPlus/sessioncommunities.online#which-sources-are-crawled"
target="_blank"
>various sources</a>.
<br> <br>
We make an attempt to hide communities containing We make an attempt to hide communities containing
objectionable or illegal content, but objectionable or illegal content, but
@ -65,8 +89,13 @@
only available with JS enabled. only available with JS enabled.
</p> </p>
<p> <p>
<label for="toggle-show-room-ids" class="clickable anchorstyle"> <label
Toggle room identifier display for="toggle-show-room-ids"
class="clickable anchorstyle"
tabindex="0"
title="Shows a column with community identifiers
for developers or language data contributors.">
Toggle room identifier display
</label> </label>
</p> </p>
<nav> <nav>
@ -83,17 +112,30 @@
<a <a
href="https://github.com/oxen-io/session-pysogs" href="https://github.com/oxen-io/session-pysogs"
target="_blank" target="_blank"
title="Information about running a community server" title="Information about running a community server."
>Host Your Own Community</a> >Host Your Own Community</a>
<a <a
href="https://getsession.org/terms-of-service" href="https://getsession.org/terms-of-service"
target="_blank" target="_blank"
>Session Terms Of Service</a> >Session Terms Of Service</a>
</nav>
<nav>
<a <a
href="https://github.com/mdPlusPlus/sessioncommunities.online" href="https://github.com/mdPlusPlus/sessioncommunities.online"
target="_blank" target="_blank"
title="sessioncommunities.online repository on GitHub." title="sessioncommunities.online repository on GitHub."
>Source Code & Contact</a> >Source Code</a>
<a
href="https://lokilocker.com/someguy/sessioncommunities.online"
target="_blank"
title="sessioncommunities.online repository on Lokinet Gitea."
>Source Code (Mirror)</a>
<a
href="https://github.com/mdPlusPlus/sessioncommunities.online#contact"
target="_blank"
rel="author"
title="Information on how to contact the maintainer of sessioncommunities.online"
>Contact</a>
</nav> </nav>
</footer> </footer>
<div id="copy-snackbar"> <div id="copy-snackbar">

@ -1,46 +1,66 @@
<?php <?php
include_once "+getenv.php"; include_once "+getenv.php";
$instruction_files = glob("+instructions/*.txt"); $instruction_files = glob("+instructions/*.txt");
function file_language($file) { return pathinfo($file)['filename']; }
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<?php include "+components/page-head.php" ?> <?php include "+components/page-head.php" ?>
<link rel="canonical" href="https://sessioncommunities.online/instructions.php">
<link rel="stylesheet" href="css/instructions.css"> <link rel="stylesheet" href="css/instructions.css">
<style type="text/css"> <style type="text/css">
<?php foreach ($instruction_files as $i => $file): ?> <?php foreach ($instruction_files as $i => $file): ?>
#language-selection-<?=$i?>:checked ~ #language-selection-<?=$i?>:checked ~
#instructions #instructions-<?=$i?> { #instructions #instructions-<?=$i?> {
display: block; display: block;
} }
<?php endforeach; ?> <?php endforeach; ?>
</style> </style>
<meta name="description" content="
Discover how to use the sessioncommunities.online website
to help you join Session Open Groups; available in <?php
// Print languages instructions are available in.
$languages = array_map('file_language', array_slice($instruction_files, 0, 10));
echo(join(", ", $languages));
?>. In short: Use the Copy button or scan the QR code!
">
<meta property="og:title" content="How to join Session Communities">
<meta property="og:description" content="Learn how to use sessioncommunities.online to join">
<meta property="og:url" content="https://sessioncommunities.online/instructions.php">
<meta property="og:type" content="article">
</head> </head>
<body> <body>
<header> <header>
<h1>Instructions for joining Session Communities</h1> <h1>Instructions for joining Session Communities</h1>
</header> </header>
<main> <main>
<p>
<a href="/" title="Return to the Session Community listing">
Go back to Community list
</a>
</p>
Choose your language: Choose your language:
<?php foreach ($instruction_files as $i => $file): ?> <?php foreach ($instruction_files as $i => $file): ?>
<br> <br>
<input <input
id="language-selection-<?=$i?>" id="language-selection-<?=$i?>"
class="language-selection" class="language-selection"
name="language" name="language"
type="radio" type="radio"
<?=file_language($file) == 'English' ? 'checked="checked"' : ''?>
> >
<label for="language-selection-<?=$i?>"> <label for="language-selection-<?=$i?>">
<?= <?=
// Name of the language // Name of the language
// Can be later parsed from i.e. first line of file // Can be later parsed from i.e. first line of file
pathinfo($file)['filename'] file_language($file);
?> ?>
</label> </label>
<?php endforeach; ?> <?php endforeach; ?>
<article id="instructions"> <article id="instructions">
<?php foreach ($instruction_files as $i => $file): ?> <?php foreach ($instruction_files as $i => $file): ?>
<section id="instructions-<?=$i?>" class="instructions"><?php <section id="instructions-<?=$i?>" class="instructions"><?php

Loading…
Cancel
Save