Merge branch 'main' into community-labels

dev
gravel 3 years ago
commit 31092d2529
Signed by: gravel
GPG Key ID: C0538F3C906B308F

1
.gitignore vendored

@ -1,6 +1,7 @@
# Generated HTML
output/*.html
# Downloaded QR codes
output/qr-codes
# Server-side cache

@ -1,11 +1,12 @@
<?php
$PROJECT_ROOT=__DIR__;
$CACHE_ROOT="$PROJECT_ROOT/cache";
$QR_CODES="$CACHE_ROOT/qr-codes";
$ROOMS_FILE="$CACHE_ROOT/rooms.json";
$DOCUMENT_ROOT="$PROJECT_ROOT/output";
$TEMPLATES_ROOT="$PROJECT_ROOT/sites";
$LANGUAGES_ROOT="$PROJECT_ROOT/languages";
$QR_CODES="$DOCUMENT_ROOT/qr-codes";
$QR_CODES_RELATIVE="qr-codes";
include_once "$PROJECT_ROOT/php/utils/logging.php";

@ -49,9 +49,10 @@ lan-server:
open:
xdg-open "http://localhost:$(PORT)" >/dev/null 2>/dev/null & disown
# Update HTML on file change. Doesn't check for new files.
# Update HTML on file change.
watchdog:
find . | grep -v ".git" | entr -n -s "$(MAKE) html"
set -o pipefail; \
while :; do find . | grep -v ".git" | entr -nds "$(MAKE) html" && exit; done
# Remove artefacts
clean:

@ -5,16 +5,14 @@
This script crawls known sources of published Session Communities,
queries their servers for available information and
displays this information as a static HTML page.
The results of this can be viewed on https://sessioncommunities.online/.
The results of this can be viewed on <https://sessioncommunities.online/>.
## What is Session?
Session is a private messaging app that protects your meta-data,
encrypts your communications, and makes sure your messaging activities
leave no digital trail behind.
https://getsession.org/
<https://getsession.org/>
## Details
@ -59,20 +57,28 @@ The details can be seen in [`curl_get_contents()`](php/utils/utils.php).
### Official repositories
- GitHub: <https://github.com/mdPlusPlus/sessioncommunities.online>
- Lokinet Gitea: <https://lokilocker.com/SomeGuy/sessioncommunities.online>
- Codeberg: <https://codeberg.org/gravel/sessioncommunities.online>
- Lokinet Gitea (mirror): <https://lokilocker.com/gravel/sessioncommunities.online>
- GitHub (former official repository): <https://github.com/mdPlusPlus/sessioncommunities.online>
- Lokinet Gitea (former mirror): <https://lokilocker.com/SomeGuy/sessioncommunities.online>
If your favourite Session community is missing a language flag,
you can issue a pull request here:
- <https://github.com/mdPlusPlus/sessioncommunities.online-languages/>
Alternatively, support this issue on Oxen Github to make language tags a native feature:
- <https://github.com/oxen-io/session-pysogs/issues/186>
### Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
## Contact
If you want to contact me, you can add me on Session via my
[ONS](https://docs.oxen.io/using-the-oxen-blockchain/using-oxen-name-system):
`someguy`.
To report issues, visit the Web Development Community on [caliban.org](http://sog.caliban.org/r/webdev).
Contact gravel on Session as `gravel` (Registered ID) or [read more](https://codeberg.org/gravel/gravel).
Contact SomeGuy on Session as `someguy` (Registered ID).

@ -1,7 +1,21 @@
body {
background-color: black;
color: white;
}
header {
text-align: center;
}
a, .anchorstyle {
color: hsl(210, 100%, 60%);
text-decoration: underline;
}
#language-selection-title, label[for^=language-selection] {
font-size: 1.2em;
}
#instructions {
padding: 1em;
}
@ -14,7 +28,7 @@ header {
font-size: 1.25em;
line-height: 1.5;
padding: 2em;
background-color: silver;
background-color: hsl(0, 0%, 9%);
border: 2px solid black;
border-radius: 1em;
}

@ -1,5 +1,4 @@
:root {
--alternate-row-color: #e8e8e8;
--body-margin: 8px; /* Default value in browsers */
--max-font-size-unitless: 18;
@ -36,6 +35,14 @@
--dynamic-columns-width: var(--expanded-dynamic-columns-width);
}
:root {
--cell-padding-h: 0.5em;
--cell-padding-v: 0.5em;
--cell-padding: var(--cell-padding-h) var(--cell-padding-v);
--cell-padding-small:
calc( var(--cell-padding-h) / 2 ) calc( var(--cell-padding-v) / 2 );
}
html {
font-size: clamp(10px, 2vw, var(--max-font-size-unitless) * 1px);
}
@ -44,17 +51,51 @@ body {
margin: 0;
}
html.js .noscript, .hidden {
#toggle-theme-switch {
display: none;
}
.clickable {
cursor: pointer;
#theming-root {
width: 100%;
height: 100%;
margin: 0;
background-color: var(--secondary-color);
color: var(--primary-color);
}
#toggle-theme-switch:not(:checked) ~ #theming-root {
--primary-color: hsl(0, 0%, 100%);
--secondary-color: hsl(0, 0%, 0%);
--secondary-color-heading: hsl(0, 0%, 17%);
--secondary-color-shaded: hsl(0, 0%, 9%);
--anchor-color: hsl(210, 100%, 60%);
--color-http: hsl(0, 0%, 17%);
--color-https: hsl(195, 53%, 21%);
}
#toggle-theme-switch:checked ~ #theming-root {
--primary-color: hsl(0, 0%, 0%);
--secondary-color: hsl(0, 0%, 100%);
--secondary-color-heading: hsl(0, 0%, 83%);
--secondary-color-shaded: hsl(0, 0%, 91%);
--anchor-color: hsl(210, 100%, 40%);
--color-http: hsl(0, 0%, 83%);
--color-https: hsl(195, 53%, 79%)
}
.anchorstyle {
color: rgb(0, 102, 204);
a, .anchorstyle {
color: var(--anchor-color);
text-decoration: underline;
cursor: pointer;
}
*, *::after, *::before {
transition: color 3s, background-color 2s;
}
html.js .noscript, .hidden {
display: none;
}
html:not(.js) .js-only {
@ -84,6 +125,10 @@ html:not(.js) .js-only {
background-color: pink;
}
gap {
flex-grow: 1000;
}
header {
display: flex;
direction: row;
@ -93,6 +138,11 @@ header {
padding-inline: var(--body-margin);
}
#header-start, #header-end {
display: flex;
gap: 0.5em;
}
#headline {
text-align: center;
flex-grow: 1;
@ -102,12 +152,6 @@ header {
#tbl_communities {
margin: 0 auto;
--cell-padding-h: 0.5em;
--cell-padding-v: 0.5em;
--cell-padding: var(--cell-padding-h) var(--cell-padding-v);
--cell-padding-small:
calc( var(--cell-padding-h) / 2 ) calc( var(--cell-padding-v) / 2 );
}
/* Cells in general */
@ -156,16 +200,21 @@ header {
}
#tbl_communities th {
background-color: lightgray;
background-color: var(--secondary-color-heading);
}
#tbl_communities tr {
background-color: var(--row-color);
}
#tbl_communities tr:nth-child(even) {
--row-color: white;
background-color: var(--secondary-color);
--row-color: var(--secondary-color);
}
#tbl_communities tr:nth-child(odd) {
--row-color: var(--alternate-row-color);
background-color: var(--alternate-row-color);
background-color: var(--secondary-color-shaded);
--row-color: var(--secondary-color-shaded);
}
/* Particular columns */
@ -251,9 +300,13 @@ header {
text-shadow: 0 0 0.5em #0003;
}
a[href^="http:"] .protocol-indicator { background-color:lightgray }
a[href^="http:"] .protocol-indicator {
background-color: var(--color-http);
}
a[href^="https:"] .protocol-indicator { background-color:lightblue }
a[href^="https:"] .protocol-indicator {
background-color: var(--color-https);
}
a[href^="http:"] .protocol-indicator::after {
content: "HTTP";
@ -316,7 +369,10 @@ a[href^="https:"] .protocol-indicator::after {
.copy_button {
font-size: 1.1em;
background-color: var(--secondary-color);
color: var(--primary-color);
padding: var(--cell-padding);
border-radius: 10%;
}
/* --- Footer --- */
@ -354,47 +410,70 @@ label[for=toggle-show-room-ids]::after {
}
/* --- QR code modals --- */
.qr-code {
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
#details-modal {
padding: 0;
width: 80vw;
max-height: 80vh;
color: var(--primary-color);
background-color: var(--secondary-color);
}
.qr-code-icon {
cursor: pointer;
#details-modal-contents {
display: flex;
position: relative;
flex-direction: row;
padding: 3em;
}
.qr-code-modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
padding-top: 100px; /* Location of the box */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
/*overflow: auto;*/ /* Enable scroll if needed */
}
.qr-code-modal-content {
background-color: #ffffff;
border: 1px solid #000000;
width: 80%;
margin: auto;
padding: 20px;
}
.qr-code-modal-close {
float: right;
font-size: 35px;
font-weight: bold;
color: #aaaaaa;
}
.qr-code-modal-close:hover,
.qr-code-modal-close:focus {
#details-modal-close {
position: absolute;
cursor: pointer;
text-decoration: none;
color: #000000;
top: 0rem;
right: 0rem;
font-size: 3.5rem;
width: 5rem;
height: 5.5rem;
text-align: center;
}
#details-modal-start {
display: flex;
flex-direction: column;
margin-right: 1em;
}
#details-modal-end {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#details-modal-start #details-modal-description {
max-height: 50vh;
overflow: auto;
}
#details-modal-start #details-modal-description-inner:empty::after {
content: "No description";
font-style: italic;
}
#details-modal-end #details-modal-qr-code {
width: 20em;
height: 20em;
margin-bottom: 1em;
}
#details-modal-end #details-modal-qr-code-label {
text-align: center;
}
#details-modal :is(#details-modal-copy-button, #details-modal-copy-staff-id) {
color: var(--primary-color);
background-color: var(--secondary-color);
border-radius: 10%;
padding: var(--cell-padding-small);
}
/* <Snackbar> */
@ -423,7 +502,6 @@ label[for=toggle-show-room-ids]::after {
}
/* Responsive properties */
.td_name .room-label:nth-of-type(5) ~ .room-label {
display: none;
}
@ -470,10 +548,6 @@ label[for=toggle-show-room-ids]::after {
:root {
--dynamic-columns-width: var(--collapsed-dynamic-columns-width);
}
.td_name .room-label:nth-of-type(1) ~ .room-label {
display: none;
}
}
@media (max-width: 500px) {
@ -481,6 +555,10 @@ label[for=toggle-show-room-ids]::after {
/* ! For when descriptions don't wrap and 100vw doesn't work. */
--dynamic-columns-width: 15rem;
}
#details-modal-contents {
flex-direction: column;
}
}
/* Animations to fade the snackbar in and out */

@ -7,14 +7,36 @@ export const dom = {
tbl_communities: () => document.getElementById("tbl_communities"),
tbl_communities_content_rows:
() => Array.from(dom.tbl_communities()?.rows)?.filter(row => !row.querySelector('th')),
community_row: (communityID) => document.getElementById(communityID),
row_info: (row) => {
/** @type {string[]} */
return {
language_flag: row.querySelector('.td_language').textContent.trim(),
name: row.querySelector('.td_name').textContent.trim(),
description: row.querySelector('.td_description').textContent.trim(),
users: parseFloat(row.querySelector('.td_users').textContent.trim()),
preview_link: row.querySelector('.td_preview a[href]').getAttribute('href'),
join_link: row.querySelector('.td_join_url a[href]').getAttribute('href'),
hostname: row.getAttribute('data-hostname'),
public_key: row.getAttribute('data-pubkey'),
staff: row.getAttribute('data-staff')
};
},
meta_timestamp: () => document.querySelector('meta[name=timestamp]'),
last_checked: () => document.getElementById("last_checked_value"),
qr_modal: (communityID) => document.getElementById(`modal_${communityID}`),
/** @return {HTMLDialogElement | null} */
details_modal: () => document.getElementById('details-modal'),
details_modal_qr_code: () => document.getElementById('details-modal-qr-code'),
join_urls: () => document.getElementsByClassName("join_url_container"),
servers_hidden: () => document.getElementById("servers_hidden"),
snackbar: () => document.getElementById("copy-snackbar")
snackbar: () => document.getElementById("copy-snackbar"),
qr_code_buttons: () => document.querySelectorAll('.qr-code-button'),
}
export const JOIN_URL_PASTE = "Copied URL to clipboard. Paste into Session app to join";
export const communityQRCodeURL = (communityID) => `qr-codes/${communityID}.png`
export const COLUMN = {
IDENTIFIER: 0, LANGUAGE: 1, NAME: 2,
DESCRIPTION: 3, USERS: 4, PREVIEW: 5,
@ -33,11 +55,20 @@ export const COMPARISON = {
};
export const ATTRIBUTES = {
ROW: {
IDENTIFIER: 'data-identifier',
PUBLIC_KEY: 'data-pubkey',
HOSTNAME: 'data-hostname',
STAFF_DATA: 'data-staff'
},
SORTING: {
ACTIVE: 'data-sort',
ASCENDING: 'data-sort-asc',
COLUMN: 'data-sorted-by',
// COLUMN_LITERAL: 'sorted-by'
},
HYDRATION: {
CONTENT: 'data-hydrate-with'
}
};

@ -1,6 +1,6 @@
// Hello reader!
// This project can be found at:
// https://lokilocker.com/someguy/sessioncommunities.online
// https://codeberg.com/gravel/sessioncommunities.online
/**
* This JavaScript file uses the JSDoc commenting style.
@ -16,7 +16,8 @@
// Import magic numbers and data
import {
dom, COLUMN, COLUMN_LITERAL, COMPARISON, ATTRIBUTES,
columnAscendingByDefault, columnIsSortable, COLUMN_TRANSFORMATION, element
columnAscendingByDefault, columnIsSortable, COLUMN_TRANSFORMATION,
element, JOIN_URL_PASTE, communityQRCodeURL
} from './js/constants.js';
// Hidden communities for transparency.
@ -45,6 +46,7 @@ const transformJoinURL = (join_link) => {
return element.button({
textContent: "Copy",
className: "copy_button",
title: "Click here to copy the join URL",
onclick: () => copyToClipboard(join_link)
});
}
@ -70,31 +72,122 @@ function onLoad() {
markSortableColumns();
addQRModalHandlers();
addServerIconInteractions();
preloadQRCodes();
}
function displayQRModal(communityID) {
dom.qr_modal(communityID).style.display = "block";
const modal = dom.details_modal();
if (!modal) {
throw new DOMException("Modal element not found.");
}
const row = dom.community_row(communityID);
if (!row) {
throw new DOMException("Community row not found.");
}
const rowInfo = dom.row_info(row);
for (const element of modal.querySelectorAll(`[${ATTRIBUTES.HYDRATION.CONTENT}]`)) {
const attributes = element.getAttribute(ATTRIBUTES.HYDRATION.CONTENT);
if (!attributes) continue;
for (const attribute of attributes.split(';')) {
const [property, targetProperty] = attribute.includes(':')
? attribute.split(":")
: [attribute, 'textContent'];
if (!Object.getOwnPropertyNames(rowInfo).includes(property)) {
console.error(`Unknown rowInfo property: ${property}`);
continue;
}
if (targetProperty === 'textContent') {
element.textContent = rowInfo[property];
} else {
element.setAttribute(targetProperty, rowInfo[property]);
}
}
}
dom.details_modal_qr_code().src = communityQRCodeURL(communityID);
modal.showModal();
}
function hideQRModal(communityID) {
dom.qr_modal(communityID).style.display = "none";
dom.details_modal().close();
}
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');
const communityID = row.getAttribute(ATTRIBUTES.ROW.IDENTIFIER);
row.querySelector('.td_qr_code').addEventListener(
'click',
() => displayQRModal(communityID)
);
row.querySelector('.td_name').addEventListener(
'click',
(e) => {
e.preventDefault();
displayQRModal(communityID);
}
);
}
const closeButton =
dom.qr_modal(communityID).querySelector('.qr-code-modal-close');
dom.details_modal().querySelector('#details-modal-close');
closeButton.addEventListener(
'click',
() => hideQRModal(communityID)
() => hideQRModal()
);
dom.details_modal().addEventListener('click', function (e) {
if (this == e.target) {
this.close();
}
});
document.querySelector('#details-modal-copy-button').addEventListener(
'click',
function () {
copyToClipboard(this.getAttribute('data-href'));
}
)
document.querySelector('#details-modal-copy-staff-id')?.addEventListener(
'click',
function () {
/**
* @type {string[]}
*/
const staff = JSON.parse(this.getAttribute(ATTRIBUTES.ROW.STAFF_DATA));
if (staff.length == 0) {
alert("No public moderators available for this Community.");
return;
}
const staffId = staff[~~(staff.length * Math.random())];
copyToClipboard(`@${staffId}`, 'Copied staff ID to clipboard.');
}
)
for (const anchor of dom.qr_code_buttons()) {
// Disable QR code links
anchor.setAttribute("href", "#");
anchor.removeAttribute("target");
anchor.addEventListener('click', (e) => { e.preventDefault(); return false });
}
}
function preloadQRCodes() {
const rows = dom.tbl_communities_content_rows();
const identifiers = rows.map(
rowElement => rowElement.getAttribute(ATTRIBUTES.ROW.IDENTIFIER)
);
for (const identifier of identifiers) {
(new Image()).src = communityQRCodeURL(identifier);
}
}
@ -133,13 +226,20 @@ function hideElementByID(id) {
/**
* Copies text to clipboard and shows an informative toast.
* @param {string} text - Text to copy to clipboard.
* @param {string} [toastText] - Text shown by toast.
*/
function copyToClipboard(text) {
function copyToClipboard(text, toastText = JOIN_URL_PASTE) {
navigator.clipboard.writeText(text);
// Find snackbar element
const snackbar = dom.snackbar();
if (!snackbar) {
throw new DOMException("Could not find snackbar");
}
snackbar.textContent = toastText;
snackbar.classList.add('show')
// After 3 seconds, hide the snackbar.
@ -163,8 +263,8 @@ function setLastChecked(last_checked) {
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 hostname = row.getAttribute(ATTRIBUTES.ROW.HOSTNAME);
const publicKey = row.getAttribute(ATTRIBUTES.ROW.PUBLIC_KEY);
const serverIcon = row.querySelector('.td_server_icon');
serverIcon.addEventListener('click', () => {
alert(`Host: ${hostname}\n\nPublic key:\n${publicKey}`);

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M48 32C21.5 32 0 53.5 0 80v96c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H48zm80 64v64H64V96h64zM48 288c-26.5 0-48 21.5-48 48v96c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48V336c0-26.5-21.5-48-48-48H48zm80 64v64H64V352h64zM256 80v96c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H304c-26.5 0-48 21.5-48 48zm64 16h64v64H320V96zm32 352v32h32V448H352zm96 0H416v32h32V448zM416 288v32H352V288H256v96 96h64V384h32v32h96V352 320 288H416z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path style="fill: grey;" d="M48 32C21.5 32 0 53.5 0 80v96c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H48zm80 64v64H64V96h64zM48 288c-26.5 0-48 21.5-48 48v96c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48V336c0-26.5-21.5-48-48-48H48zm80 64v64H64V352h64zM256 80v96c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H304c-26.5 0-48 21.5-48 48zm64 16h64v64H320V96zm32 352v32h32V448H352zm96 0H416v32h32V448zM416 288v32H352V288H256v96 96h64V384h32v32h96V352 320 288H416z"/></svg>

Before

Width:  |  Height:  |  Size: 725 B

After

Width:  |  Height:  |  Size: 745 B

@ -0,0 +1,39 @@
<?php
/**
* Return local path to room invite code.
* @param string $room_id Id of room to locate QR code for.
*/
function room_qr_code_path(string $room_id): string {
global $QR_CODES;
return "$QR_CODES/$room_id.png";
}
/**
* Return remote path to room invite code.
* @param string $room_id Id of room to locate QR code for.
*/
function room_qr_code_path_relative(string $room_id): string {
global $QR_CODES_RELATIVE;
return "$QR_CODES_RELATIVE/$room_id.png";
}
/**
* Fetch QR invite of the given room and return its local path.
* @param \CommunityRoom $room
* @return string
*/
function room_qr_code($room): string {
$room_id = $room->get_room_identifier();
$png_cached = room_qr_code_path($room_id);
if (file_exists($png_cached)) {
return room_qr_code_path_relative($room_id);
}
log_debug("Fetching QR code for $room_id.");
$png = file_get_contents($room->get_invite_url());
file_put_contents($png_cached, $png);
return room_qr_code_path_relative($room_id);
}
file_exists($QR_CODES) or mkdir($QR_CODES, 0700);
?>

@ -1,46 +1,74 @@
<?php
/**
* @var CommunityRoom[] $rooms
*/
function room_qr_code_cached($room_id) {
global $QR_CODES;
return "$QR_CODES/$room_id.png";
}
/**
* Fetch QR codes from SOGS server and encode them as base64
* @param CommunityRoom $room
*/
function base64_qr_code($room, $size = "512x512") {
$room_id = $room->get_room_identifier();
$png_cached = room_qr_code_cached($room_id);
if (file_exists($png_cached)) {
return base64_encode(file_get_contents($png_cached));
}
log_debug("Fetching QR code for $room_id.");
$png = file_get_contents($room->get_invite_url());
file_put_contents($png_cached, $png);
return base64_encode($png);
}
file_exists($QR_CODES) or mkdir($QR_CODES, 0700);
require_once "$PROJECT_ROOT/php/utils/room-invites.php";
?>
<div id="modal-container">
<?php foreach ($rooms as $room): ?>
<div id="modal_<?=$room->get_room_identifier()?>" class="qr-code-modal">
<div class="qr-code-modal-content">
<span class="qr-code-modal-close">
<dialog id="details-modal">
<div id="details-modal-contents">
<div id="details-modal-close">
&times;
</span>
</div>
<div id="details-modal-start">
<h1 id="details-modal-title">
<a
id="details-modal-community-name"
data-hydrate-with="name;preview_link:href"
title="Open preview in new tab"
></a>
</h1>
<p id="details-modal-description">
<span>Description:</span>
<span id="details-modal-description-inner" data-hydrate-with="description"></span>
</p>
<gap></gap>
<div id="details-modal-room-info">
<p>
Language: <span data-hydrate-with="language_flag"></span>
</p>
<p>
Users: <span data-hydrate-with="users"></span>
</p>
<p>
Server:
<a
title="Open server in new tab"
data-hydrate-with="hostname;hostname:href"
target="_blank"
rel="noopener noreferrer"
></a>
</p>
<p>
<button
id="details-modal-copy-button"
data-hydrate-with="join_link:data-href"
title="Click here to copy this Community's join link"
>
Copy join link
</button>
<button
id="details-modal-copy-staff-id"
data-hydrate-with="staff:data-staff"
title="Copy the mention for a random staff member"
>
Copy mod ID
</button>
</p>
</div>
</div>
<gap></gap>
<div id="details-modal-end">
<img
src="data:image/png;base64,<?=base64_qr_code($room)?>"
alt="Community join link encoded as QR code"
class="qr-code"
loading="lazy"
src=""
id="details-modal-qr-code"
title="Community join link encoded as QR code"
>
<div id="details-modal-qr-code-label">
Scan QR code in Session to join
<br>
'<span data-hydrate-with="name"></span>'
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</dialog>

@ -1,6 +1,7 @@
<?php
require_once "$PROJECT_ROOT/php/utils/utils.php";
require_once "$PROJECT_ROOT/php/utils/servers-rooms.php";
require_once "$PROJECT_ROOT/php/utils/room-invites.php";
/**
* @var CommunityRoom[] $rooms
@ -17,7 +18,7 @@
$column = $TABLE_COLUMNS[$colno];
$name = isset($column['name_long']) ? $column['name_long'] : $column['name'];
if (!column_sortable($column['id'])) return " title='$name'";
return " title='Click to sort by $name'.";
return " title='Click to sort by $name.'";
}
// Note: Changing the names displayed requires updating
@ -63,12 +64,15 @@
$join_link = html_sanitize($room->get_join_url());
$pubkey = html_sanitize($pubkey);
$hostname = html_sanitize($hostname);
$staff_json = json_encode(array_map('html_sanitize', $room->get_staff()));
?>
<tr id="<?=$id?>" itemscope itemtype="https://schema.org/EntryPoint"
<tr id="<?=$id?>" class="room-row" itemscope itemtype="https://schema.org/EntryPoint"
data-identifier="<?=$id?>"
data-pubkey="<?=$pubkey?>"
data-hostname="<?=$hostname?>"
data-staff='<?=$staff_json?>'
>
<td class="td_identifier" itemprop="identifier"><?=$id?></td>
<td class="td_language" title="Language flag for '<?=$name?>'"><?=$language?></td>
@ -117,12 +121,18 @@
</a>
</td>
<td class="td_qr_code">
<a
class="qr-code-button"
href="<?=room_qr_code($room)?>"
target="_blank"
>
<img
class="qr-code-icon"
src="qrcode-solid.svg"
alt="Pictogram of a QR code"
title="Click here to view the QR Code for '<?=$name?>'"
title="Click here to view the details for '<?=$name?>'"
>
</a>
</td>
<td class="td_server_icon"
data-sort-by="<?=$pubkey?>"
@ -135,8 +145,8 @@
</td>
<td class="td_join_url">
<div class="join_url_container" data-url="<?=$join_link?>">
<a class="join_url show-from-w5" title="<?=$join_link?>"
><?=truncate($join_link, 32)?></a>
<span class="join_url show-from-w5" title="<?=$join_link?>"
><?=truncate($join_link, 32)?></span>
<a
class="noscript"
title="Copy this link to join '<?=$name?>'."

@ -31,7 +31,7 @@
<?php include "+components/page-head.php" ?>
<link rel="canonical" href="https://sessioncommunities.online/">
<link rel="stylesheet" href="styles2.css">
<link rel="stylesheet" href="./index.css">
<script type="module" src="./main.js"></script>
<title>Self-updating list of active Session communities</title>
<meta name="description" content="
@ -51,9 +51,18 @@
<meta name="timestamp" content="<?=$timestamp?>">
</head>
<body>
<input type="checkbox" id="toggle-theme-switch">
<div id="theming-root">
<header>
<div id="header-start"></div>
<div id="header-end">
<label
for="toggle-theme-switch"
class="anchorstyle"
title="Switch color theme"
>
Switch theme
</label>
<a
id="link-instructions"
target="_blank"
@ -92,7 +101,7 @@
<br>
Communities shown are fetched automatically from
<a
href="https://github.com/mdPlusPlus/sessioncommunities.online#which-sources-are-crawled"
href="https://codeberg.org/gravel/sessioncommunities.online#which-sources-are-crawled"
target="_blank"
>various sources</a>.
<br>
@ -144,25 +153,19 @@
</nav>
<nav>
<a
href="https://github.com/mdPlusPlus/sessioncommunities.online"
href="https://codeberg.org/gravel/sessioncommunities.online"
target="_blank"
title="sessioncommunities.online repository on GitHub."
title="sessioncommunities.online repository on Codeberg."
>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"
href="https://codeberg.org/gravel/sessioncommunities.online#contact"
target="_blank"
rel="author"
title="Information on how to contact the maintainer of sessioncommunities.online"
>Contact</a>
</nav>
</footer>
<div id="copy-snackbar">
Copied URL to clipboard. Paste into Session app to join
</div>
<div id="copy-snackbar"></div>
</body>
</html>

@ -41,9 +41,11 @@
Go back to Community list
</a>
</p>
<span id="language-selection-title">
Choose your language:
<?php foreach ($instruction_files as $i => $file): ?>
</span>
<br>
<?php foreach ($instruction_files as $i => $file): ?>
<input
id="language-selection-<?=$i?>"
class="language-selection"

Loading…
Cancel
Save