diff --git a/output/js/util.js b/output/js/util.js index 34111f4..edf52be 100644 --- a/output/js/util.js +++ b/output/js/util.js @@ -322,6 +322,7 @@ function createElement(tag, ...args) { return element; } +/** @type {Record} */ export const element = new Proxy({}, { get(_, key) { return (...args) => createElement(key, ...args) diff --git a/output/main.js b/output/main.js index 36b9b77..dc846db 100644 --- a/output/main.js +++ b/output/main.js @@ -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 `); + + 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