diff --git a/.phpenv b/.phpenv index 309b041..3c59c08 100644 --- a/.phpenv +++ b/.phpenv @@ -16,4 +16,6 @@ // do not report warnings (timeouts, SSL/TLS errors) error_reporting(E_ALL & ~E_WARNING); + + date_default_timezone_set('UTC'); ?> \ No newline at end of file diff --git a/output/js/constants.js b/output/js/constants.js index f313728..3002622 100644 --- a/output/js/constants.js +++ b/output/js/constants.js @@ -6,7 +6,7 @@ export const dom = { tbl_communities: () => document.getElementById("tbl_communities"), last_checked: () => document.getElementById("last_checked_value"), qr_modal: (communityID) => document.getElementById(`modal_${communityID}`), - join_urls: () => document.getElementsByClassName("td_join_url"), + join_urls: () => document.getElementsByClassName("join_url_container"), servers_hidden: () => document.getElementById("servers_hidden"), snackbar: () => document.getElementById("copy-snackbar") } @@ -57,3 +57,29 @@ export function columnIsNumeric(column) { ].includes(column); } +/** + * Creates an element, and adds attributes and elements to it. + * @param {string} tag - HTML Tag name. + * @param {Object|HTMLElement} args - Array of child elements, may start with props. + * @returns {HTMLElement} + */ +function createElement(tag, ...args) { + const element = document.createElement(tag); + if (args.length === 0) return element; + const propsCandidate = args[0]; + if (typeof propsCandidate !== "string" && !(propsCandidate instanceof Element)) { + // args[0] is not child element or text node + // must be props object + Object.assign(element, propsCandidate); + args.shift(); + } + element.append(...args); + return element; +} + +export const element = new Proxy({}, { + get(_, key) { + return (...args) => createElement(key, ...args) + } +}); + diff --git a/output/main.js b/output/main.js index 5580094..0bc0bdb 100644 --- a/output/main.js +++ b/output/main.js @@ -17,7 +17,7 @@ import { dom, COLUMN, COLUMN_LITERAL, COMPARISON, ATTRIBUTES, columnAscendingByDefault, columnIsSortable, columnNeedsCasefold, - columnIsNumeric + columnIsNumeric, element } from './js/constants.js'; // Hidden communities for transparency. @@ -50,12 +50,13 @@ const filteredCommunities = { // This can be achieved with `text-overflow: ellipsis` instead // and generated entirely server-side. -const transformJoinURL = (join_link) => - `${join_link.substring(0, 31)}... - - `.trim(); +const transformJoinURL = (join_link) => { + return element.button({ + textContent: "Copy", + className: "copy_button", + onclick: () => copyToClipboard(join_link) + }); +} function onLoad(timestamp) { setLastChecked(timestamp); @@ -78,7 +79,7 @@ function createJoinLinkButtons() { Array.from(join_URLs).forEach((td_url) => { const a_href = td_url.querySelector('a'); // get first (only) element const join_link = a_href.getAttribute("href"); // get link - td_url.innerHTML = transformJoinURL(join_link); // add interactive content + td_url.append(transformJoinURL(join_link)); // add interactive content }); } @@ -131,7 +132,7 @@ function setLastChecked(last_checked) { const time_passed_in_minutes = Math.floor(time_passed_in_seconds / 60); // time in minutes, rounded down const timestamp_element = dom.last_checked(); - timestamp_element.innerText = `${time_passed_in_minutes} minutes`; + timestamp_element.innerText = `${time_passed_in_minutes} minutes ago`; } /** @@ -279,6 +280,9 @@ function sortTable(column) { setSortState(table, { ascending, column }); } +// html.js for styling purposes +window.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, { diff --git a/output/styles2.css b/output/styles2.css index 68a710b..432dfee 100644 --- a/output/styles2.css +++ b/output/styles2.css @@ -1,60 +1,98 @@ +html { + font-size: clamp(10px, 2vw, 18px); +} + +html.js .noscript { + display: none; +} + +/* Dead style */ +html:not(.js) .js-only { + display: none; +} + header { - display: flex; - direction: row; - /* Push items as far apart as possible */ - justify-content: space-between; + display: flex; + direction: row; + /* Push items as far apart as possible */ + justify-content: space-between; } #headline { - text-align: center; - flex-grow: 1; + text-align: center; + flex-grow: 1; } #tbl_communities { width:100%; } -#th_identifier { width:9%; } -.td_identifier { font-family: monospace; } -#th_name { width:13%; } -#th_language { width:0% } +/* Hide the identifier column before removal. */ +#th_identifier { display: none; } +.td_identifier { display: none; font-family: monospace; } + +.td_language:empty::after { + content: "\2753"; +} + #th_description { } .td_description { overflow: hidden; text-overflow: ellipsis; - display: - -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 3; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; } -#th_users { width:0% } + .td_users { text-align: right; } -#th_preview { max-width:5%; } .td_preview { text-align: center; } -#th_qr { width:0%; } -#th_join_url { width:15%; } .td_join_url { font-family: monospace; white-space: nowrap; + font-size: .8em; } -.copy_button { } +.join_url { + /* Apply margin against copy button or link. */ + /* URL now guaranteed to have interactive element to right when present. */ + margin-right: 1em; +} + + +@media (max-width: 950px) { + /* Only current width breakpoint; */ + /* Would follow w4 and precede w6. */ + .show-from-w5 { + display: none; + } +} + +.join_url_container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.copy_button { font-size: inherit } footer { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - text-align: center; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + text-align: center; } footer p { - width: 75%; - margin: .5em; - text-align: center; + width: 75%; + margin: .5em; + text-align: center; } footer nav a { - margin: 0 .5ch; + display: inline-block; + margin: .25em; + white-space: nowrap; } /* */ @@ -73,8 +111,8 @@ footer nav a { display: inline-block; font-family: monospace; border-radius: 4px; + padding: .25em .05em; width: 6ch; - line-height: 22px; text-align: center; } .protocol-http { background-color:lightgray } @@ -170,3 +208,4 @@ footer nav a { from {bottom: 30px; opacity: 1;} to {bottom: 0; opacity: 0;} } + diff --git a/php/utils/server-utils.php b/php/utils/server-utils.php index c0e91a3..f6ac141 100644 --- a/php/utils/server-utils.php +++ b/php/utils/server-utils.php @@ -18,6 +18,12 @@ return count($servers); } + function truncate($url, $len) { + return (strlen($url) > $len + 3) + ? substr($url, 0, $len).'...' + : $string; + } + /* * Helper function for reduce_servers */ diff --git a/sites/+components/page-head.php b/sites/+components/page-head.php index d5bde1f..730c42f 100644 --- a/sites/+components/page-head.php +++ b/sites/+components/page-head.php @@ -1,2 +1,3 @@ + \ No newline at end of file diff --git a/sites/+components/qr_modals.php b/sites/+components/qr_modals.php index eb9d569..ca267ce 100644 --- a/sites/+components/qr_modals.php +++ b/sites/+components/qr_modals.php @@ -53,27 +53,29 @@ // file_exists($QR_CODES) or mkdir($QR_CODES, 0700); // @Deprecated ?> +