|
|
|
@ -15,7 +15,7 @@
|
|
|
|
|
|
|
|
|
|
// Import magic numbers and data
|
|
|
|
|
import {
|
|
|
|
|
dom, COLUMN, COLUMN_LITERAL, COMPARISON, ATTRIBUTES,
|
|
|
|
|
dom, COMPARISON, ATTRIBUTES,
|
|
|
|
|
columnAscendingByDefault, columnIsSortable, COLUMN_TRANSFORMATION,
|
|
|
|
|
element, JOIN_URL_PASTE, communityQRCodeURL, STAFF_ID_PASTE, IDENTIFIER_PASTE, DETAILS_LINK_PASTE, CLASSES, flagToLanguageAscii, RoomInfo, unreachable, workOnMainThread, onInteractive
|
|
|
|
|
} from './js/util.js';
|
|
|
|
@ -64,7 +64,6 @@ let shownCommunityId = "";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create an interactive version of the Community join link.
|
|
|
|
|
* @param {string} join_link
|
|
|
|
|
* @returns {HTMLElement}
|
|
|
|
|
*/
|
|
|
|
|
const transformJoinURL = () => {
|
|
|
|
@ -153,6 +152,19 @@ function addInformativeInteractions() {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function enableEnterClicks() {
|
|
|
|
|
Array.from(document.querySelectorAll('.enter-clicks')).forEach(element => {
|
|
|
|
|
if (!(element instanceof HTMLElement)) return;
|
|
|
|
|
element.addEventListener('keydown', (/** @type {KeyboardEvent} */ ev) => {
|
|
|
|
|
if (ev.key == "Enter") {
|
|
|
|
|
if (!(ev.currentTarget instanceof HTMLElement)) {
|
|
|
|
|
console.error(".enter-clicks could not find its currentTarget");
|
|
|
|
|
} else ev.currentTarget.click();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fetchTableRestFragment() {
|
|
|
|
|
if (location.pathname != "/") return;
|
|
|
|
|
const request = await fetch("/_fragment/rest/");
|
|
|
|
@ -182,14 +194,7 @@ async function onLoad() {
|
|
|
|
|
preloadImages();
|
|
|
|
|
}, 60 * 60E3);
|
|
|
|
|
addInformativeInteractions();
|
|
|
|
|
Array.from(document.querySelectorAll('.enter-clicks')).forEach(element => {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
element.addEventListener('keydown', (/** @type {KeyboardEvent} */ ev) => {
|
|
|
|
|
if (ev.key == "Enter") {
|
|
|
|
|
ev.currentTarget.click();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
enableEnterClicks();
|
|
|
|
|
await RoomInfo.fetchRooms();
|
|
|
|
|
reactToURLParameters();
|
|
|
|
|
addServerIconInteractions();
|
|
|
|
@ -200,7 +205,7 @@ async function onLoad() {
|
|
|
|
|
* Construct room tag DOM from its description.
|
|
|
|
|
* @param {Object} param0
|
|
|
|
|
* @param {string} param0.text Tag name
|
|
|
|
|
* @param {"user"|"reserved"} param0.type Tag classification
|
|
|
|
|
* @param {string} param0.type Tag classification
|
|
|
|
|
* @param {string} param0.description Tag details
|
|
|
|
|
* @returns HTMLElement
|
|
|
|
|
*/
|
|
|
|
@ -253,15 +258,22 @@ function displayQRModal(communityID, pane = 0) {
|
|
|
|
|
|
|
|
|
|
const tagContainer = dom.details_modal_tag_container();
|
|
|
|
|
|
|
|
|
|
tagContainer.innerHTML = "";
|
|
|
|
|
if (tagContainer) {
|
|
|
|
|
|
|
|
|
|
tagContainer.append(
|
|
|
|
|
...rowInfo.tags.map(tag => tagBody(tag))
|
|
|
|
|
);
|
|
|
|
|
tagContainer.innerHTML = "";
|
|
|
|
|
|
|
|
|
|
dom.details_modal_qr_code().src = communityQRCodeURL(communityID);
|
|
|
|
|
tagContainer.append(
|
|
|
|
|
...rowInfo.tags.map(tag => tagBody(tag))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
} else console.error(`Could not find tag container for ${communityID}`);
|
|
|
|
|
|
|
|
|
|
document.getElementById('details-modal-panes').setAttribute('data-pane', pane);
|
|
|
|
|
const qrCode = dom.details_modal_qr_code();
|
|
|
|
|
if (qrCode instanceof HTMLImageElement) {
|
|
|
|
|
qrCode.src = communityQRCodeURL(communityID);
|
|
|
|
|
} else console.error(`Could not find QR code <img>`);
|
|
|
|
|
|
|
|
|
|
document.getElementById('details-modal-panes')?.setAttribute('data-pane', `${pane}`);
|
|
|
|
|
|
|
|
|
|
location.hash=`#${communityID}`;
|
|
|
|
|
|
|
|
|
@ -272,7 +284,8 @@ function displayQRModal(communityID, pane = 0) {
|
|
|
|
|
* Hides the Community details modal.
|
|
|
|
|
*/
|
|
|
|
|
function hideQRModal() {
|
|
|
|
|
dom.details_modal().close();
|
|
|
|
|
const detailsModal = dom.details_modal();
|
|
|
|
|
if (detailsModal) detailsModal.close(); else console.error("hideQRModal(): detailsModal is null")
|
|
|
|
|
shownCommunityId = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -301,7 +314,7 @@ function addQRModalHandlers() {
|
|
|
|
|
displayQRModal(communityID);
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
row.querySelector('.td_name').addEventListener(
|
|
|
|
|
row.querySelector('.td_name')?.addEventListener(
|
|
|
|
|
'click',
|
|
|
|
|
(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
@ -311,17 +324,20 @@ function addQRModalHandlers() {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const closeButton =
|
|
|
|
|
dom.details_modal().querySelector('#details-modal-close');
|
|
|
|
|
closeButton.addEventListener(
|
|
|
|
|
'click',
|
|
|
|
|
() => hideQRModal()
|
|
|
|
|
);
|
|
|
|
|
dom.details_modal().addEventListener('click', function (e) {
|
|
|
|
|
if (this == e.target) {
|
|
|
|
|
this.close();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
const detailsModal = dom.details_modal();
|
|
|
|
|
|
|
|
|
|
if (detailsModal) {
|
|
|
|
|
const closeButton = detailsModal.querySelector('#details-modal-close');
|
|
|
|
|
closeButton?.addEventListener(
|
|
|
|
|
'click',
|
|
|
|
|
() => hideQRModal()
|
|
|
|
|
);
|
|
|
|
|
detailsModal.addEventListener('click', function (e) {
|
|
|
|
|
if (this == e.target) {
|
|
|
|
|
this.close();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else console.error("Could not find details modal");
|
|
|
|
|
|
|
|
|
|
for (const button of document.querySelectorAll('.details-modal-pane-button')) {
|
|
|
|
|
button.addEventListener(
|
|
|
|
@ -333,7 +349,7 @@ function addQRModalHandlers() {
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.querySelector('#details-modal-copy-button').addEventListener(
|
|
|
|
|
document.querySelector('#details-modal-copy-button')?.addEventListener(
|
|
|
|
|
'click',
|
|
|
|
|
function () {
|
|
|
|
|
copyToClipboard(this.getAttribute('data-href'));
|
|
|
|
@ -391,7 +407,8 @@ function addQRModalHandlers() {
|
|
|
|
|
const increment = isLeftArrowKey ? -1 : 1;
|
|
|
|
|
const newRowIndex = (shownRowIndex + increment + communityRows.length) % communityRows.length;
|
|
|
|
|
const newRowIdentifier = communityRows[newRowIndex].identifier;
|
|
|
|
|
displayQRModal(newRowIdentifier);
|
|
|
|
|
if (newRowIdentifier === null) console.error("newRowIdentifier is null");
|
|
|
|
|
else displayQRModal(newRowIdentifier);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
@ -637,6 +654,7 @@ function compareAscending(fst, snd) {
|
|
|
|
|
// Triple equals to avoid "" == 0.
|
|
|
|
|
if (fst === "") return COMPARISON.GREATER;
|
|
|
|
|
if (snd === "") return COMPARISON.SMALLER;
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
return (fst > snd) - (fst < snd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -699,8 +717,16 @@ function getSortState(table) {
|
|
|
|
|
const directionState = table.getAttribute(ATTRIBUTES.SORTING.ASCENDING);
|
|
|
|
|
// This is not pretty, but the least annoying.
|
|
|
|
|
// Checking for classes would be more idiomatic.
|
|
|
|
|
if (directionState === null) {
|
|
|
|
|
console.error("directionState was null");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const ascending = directionState.toString() === "true";
|
|
|
|
|
const columnState = table.getAttribute(ATTRIBUTES.SORTING.COLUMN);
|
|
|
|
|
if (columnState === null) {
|
|
|
|
|
console.error("columnState was null");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const column = parseInt(columnState);
|
|
|
|
|
if (!Number.isInteger(column)) {
|
|
|
|
|
throw new Error(`Invalid column number read from table: ${columnState}`)
|
|
|
|
@ -715,22 +741,23 @@ function getSortState(table) {
|
|
|
|
|
*/
|
|
|
|
|
function setSortState(table, { ascending, column }) {
|
|
|
|
|
if (!table.hasAttribute(ATTRIBUTES.SORTING.ACTIVE)) {
|
|
|
|
|
table.setAttribute(ATTRIBUTES.SORTING.ACTIVE, true);
|
|
|
|
|
table.setAttribute(ATTRIBUTES.SORTING.ACTIVE, `${true}`);
|
|
|
|
|
}
|
|
|
|
|
table.setAttribute(ATTRIBUTES.SORTING.ASCENDING, ascending);
|
|
|
|
|
table.setAttribute(ATTRIBUTES.SORTING.COLUMN, column);
|
|
|
|
|
table.setAttribute(ATTRIBUTES.SORTING.ASCENDING, `${ascending}`);
|
|
|
|
|
table.setAttribute(ATTRIBUTES.SORTING.COLUMN, `${column}`);
|
|
|
|
|
|
|
|
|
|
// No way around this for brief CSS.
|
|
|
|
|
const headers = table.querySelectorAll("th");
|
|
|
|
|
headers.forEach((th, colno) => {
|
|
|
|
|
th.removeAttribute(ATTRIBUTES.SORTING.ACTIVE);
|
|
|
|
|
});
|
|
|
|
|
headers[column].setAttribute(ATTRIBUTES.SORTING.ACTIVE, true);
|
|
|
|
|
headers[column].setAttribute(ATTRIBUTES.SORTING.ACTIVE, `${true}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This is best done in JS, as it would require <noscript> styles otherwise.
|
|
|
|
|
function markSortableColumns() {
|
|
|
|
|
const table = dom.tbl_communities();
|
|
|
|
|
if (!table) throw new Error("markSortableColumns(): could not find table");
|
|
|
|
|
const header_cells = table.querySelectorAll('th');
|
|
|
|
|
for (let colno = 0; colno < header_cells.length; colno++) {
|
|
|
|
|
if (!columnIsSortable(colno)) continue;
|
|
|
|
|