// This file contains definitions which help to reduce the amount // of redundant values in the main file, especially those that could // change in the foreseeable future. export const dom = { /** @return {HTMLTableElement | null} */ 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.querySelector(`.room-row[${ATTRIBUTES.ROW.IDENTIFIER}="${communityID}"]`), row_info: (row) => { const dateCreated = new Date(row.getAttribute(ATTRIBUTES.ROW.DATE_CREATED) * 1000); /** @type {string[]} */ return { language_flag: row.querySelector('.td_language').textContent.trim(), name: row.querySelector('.td_name-inner').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'), identifier: row.getAttribute(ATTRIBUTES.ROW.IDENTIFIER), hostname: row.getAttribute(ATTRIBUTES.ROW.HOSTNAME), public_key: row.getAttribute(ATTRIBUTES.ROW.PUBLIC_KEY), staff: row.getAttribute(ATTRIBUTES.ROW.STAFF_DATA), tags: row.getAttribute(ATTRIBUTES.ROW.TAGS), icon: row.getAttribute(ATTRIBUTES.ROW.ROOM_ICON), has_icon: row.getAttribute(ATTRIBUTES.ROW.ROOM_ICON).trim() != "", icon_safety: row.getAttribute(ATTRIBUTES.ROW.ROOM_ICON_SAFETY), date_created: dateCreated, creation_datestring: dateCreated.toLocaleDateString(undefined, {dateStyle: "medium"}) }; }, meta_timestamp: () => document.querySelector('meta[name=timestamp]'), last_checked: () => document.getElementById("last_checked_value"), /** @return {HTMLDialogElement | null} */ details_modal: () => document.getElementById('details-modal'), details_modal_tag_container: () => document.getElementById('details-modal-room-tags'), details_modal_qr_code: () => document.getElementById('details-modal-qr-code'), details_modal_room_icon: () => document.getElementById('details-modal-community-icon'), join_urls: () => document.getElementsByClassName("join_url_container"), servers_hidden: () => document.getElementById("servers_hidden"), 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 STAFF_ID_PASTE = "Copied staff ping to clipboard. Use it in the selected Community to alert a random moderator."; export const IDENTIFIER_PASTE = "Copied internal room identifier. Use it to identify a room, such as when contributing language labels." export const DETAILS_LINK_PASTE = "Copied link to Community details."; export const communityQRCodeURL = (communityID) => `qr-codes/${communityID}.png` export const COLUMN = { LANGUAGE: 0, NAME: 1, DESCRIPTION: 2, USERS: 3, PREVIEW: 4, QR_CODE: 5, SERVER_ICON: 6, JOIN_URL: 7 }; // Reverse enum. // Takes original key-value pairs, flips them, and casefolds the new values. // Should correspond to #th_{} and .td_{} elements in communities table. export const COLUMN_LITERAL = Object.fromEntries( Object.entries(COLUMN).map(([name, id]) => [id, name.toLowerCase()]) ); export const COMPARISON = { GREATER: 1, EQUAL: 0, SMALLER: -1 }; export const ATTRIBUTES = { ROW: { TAGS: 'data-tags', IDENTIFIER: 'data-identifier', PUBLIC_KEY: 'data-pubkey', HOSTNAME: 'data-hostname', STAFF_DATA: 'data-staff', ROOM_ICON: 'data-icon', ROOM_ICON_SAFETY: 'data-icon-safe', DATE_CREATED: 'data-created' }, SORTING: { ACTIVE: 'data-sort', ASCENDING: 'data-sort-asc', COLUMN: 'data-sorted-by', // COLUMN_LITERAL: 'sorted-by' }, HYDRATION: { CONTENT: 'data-hydrate-with' } }; export function columnAscendingByDefault(column) { return column != COLUMN.USERS; } export function columnIsSortable(column) { return ![ COLUMN.QR_CODE, COLUMN.PREVIEW, // Join URL contents are not guaranteed to have visible text. COLUMN.JOIN_URL ].includes(column); } /** * @type {Record any>} */ const TRANSFORMATION = { numeric: (el) => parseInt(el.innerText), casefold: (el) => el.innerText.toLowerCase().trim(), getSortKey: (el) => el.getAttribute("data-sort-by") } /** * @type {Dictionary any>} */ export const COLUMN_TRANSFORMATION = { [COLUMN.USERS]: TRANSFORMATION.numeric, [COLUMN.IDENTIFIER]: TRANSFORMATION.casefold, [COLUMN.NAME]: TRANSFORMATION.getSortKey, [COLUMN.DESCRIPTION]: TRANSFORMATION.casefold, [COLUMN.SERVER_ICON]: TRANSFORMATION.getSortKey } /** * 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) } });