|
|
@ -17,7 +17,7 @@
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
dom, COLUMN, COLUMN_LITERAL, COMPARISON, ATTRIBUTES,
|
|
|
|
dom, COLUMN, COLUMN_LITERAL, COMPARISON, ATTRIBUTES,
|
|
|
|
columnAscendingByDefault, columnIsSortable, COLUMN_TRANSFORMATION,
|
|
|
|
columnAscendingByDefault, columnIsSortable, COLUMN_TRANSFORMATION,
|
|
|
|
element, JOIN_URL_PASTE, communityQRCodeURL, STAFF_ID_PASTE, IDENTIFIER_PASTE, DETAILS_LINK_PASTE, CLASSES, flagToLanguageAscii, RoomInfo
|
|
|
|
element, JOIN_URL_PASTE, communityQRCodeURL, STAFF_ID_PASTE, IDENTIFIER_PASTE, DETAILS_LINK_PASTE, CLASSES, flagToLanguageAscii, RoomInfo, unreachable, workOnMainThread
|
|
|
|
} from './js/util.js';
|
|
|
|
} from './js/util.js';
|
|
|
|
|
|
|
|
|
|
|
|
// Hidden communities for transparency.
|
|
|
|
// Hidden communities for transparency.
|
|
|
@ -539,7 +539,9 @@ function addSearchInteractions() {
|
|
|
|
if (ev.key === "Enter") {
|
|
|
|
if (ev.key === "Enter") {
|
|
|
|
this.blur();
|
|
|
|
this.blur();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
useSearchTerm(this.value);
|
|
|
|
if (this.value === "") {
|
|
|
|
|
|
|
|
useSearchTerm("");
|
|
|
|
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
dom.btn_clear_search()?.addEventListener('click', function () {
|
|
|
|
dom.btn_clear_search()?.addEventListener('click', function () {
|
|
|
@ -620,12 +622,12 @@ function makeRowComparer(column, ascending) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Callback to obtain sortable content from cell text.
|
|
|
|
// Callback to obtain sortable content from cell text.
|
|
|
|
const columnToSortable = COLUMN_TRANSFORMATION[column] ?? ((el) => el.innerText.trim());
|
|
|
|
const rowToSortable = COLUMN_TRANSFORMATION[column];
|
|
|
|
|
|
|
|
|
|
|
|
// Construct comparer using derived property to determine sort order.
|
|
|
|
// Construct comparer using derived property to determine sort order.
|
|
|
|
const rowComparer = compareProp(
|
|
|
|
const rowComparer = compareProp(
|
|
|
|
ascending ? compareAscending : compareDescending,
|
|
|
|
ascending ? compareAscending : compareDescending,
|
|
|
|
row => columnToSortable(row.children[column], row)
|
|
|
|
({identifier}) => rowToSortable(identifier)
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return rowComparer;
|
|
|
|
return rowComparer;
|
|
|
@ -691,18 +693,25 @@ function markSortableColumns() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @type {HTMLTableRowElement[]}
|
|
|
|
* @type {{row: HTMLTableRowElement, identifier: string}[]}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
const communityFullRowCache = [];
|
|
|
|
const communityFullRowCache = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getAllCachedRows() {
|
|
|
|
|
|
|
|
return communityFullRowCache.map(({row}) => row);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function initializeSearch() {
|
|
|
|
function initializeSearch() {
|
|
|
|
communityFullRowCache.push(...dom.tbl_communities_content_rows());
|
|
|
|
communityFullRowCache.push(...dom.tbl_communities_content_rows().map(row => ({
|
|
|
|
|
|
|
|
row,
|
|
|
|
|
|
|
|
identifier: row.getAttribute(ATTRIBUTES.ROW.IDENTIFIER) ?? unreachable()
|
|
|
|
|
|
|
|
})));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @param {string} [rawTerm]
|
|
|
|
* @param {string} rawTerm
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function useSearchTerm(rawTerm, fillSearchBarWithTerm = false) {
|
|
|
|
async function useSearchTerm(rawTerm, fillSearchBarWithTerm = false) {
|
|
|
|
const searchBar = dom.search_bar();
|
|
|
|
const searchBar = dom.search_bar();
|
|
|
|
|
|
|
|
|
|
|
|
if (searchBar === undefined || !(searchBar instanceof HTMLInputElement)) {
|
|
|
|
if (searchBar === undefined || !(searchBar instanceof HTMLInputElement)) {
|
|
|
@ -710,47 +719,64 @@ function useSearchTerm(rawTerm, fillSearchBarWithTerm = false) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!rawTerm) {
|
|
|
|
if (!rawTerm) {
|
|
|
|
replaceRowsWith(communityFullRowCache);
|
|
|
|
replaceRowsWith(getAllCachedRows());
|
|
|
|
dom.search_bar()?.classList.remove(CLASSES.SEARCH.NO_RESULTS);
|
|
|
|
dom.search_bar()?.classList.remove(CLASSES.SEARCH.NO_RESULTS);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
const term = rawTerm.toLowerCase().replace(/lang:(\S+)/g, "").trim();
|
|
|
|
const term = rawTerm.toLowerCase().replace(/lang:(\S+)/g, "").trim();
|
|
|
|
const termTags = Array.from(rawTerm.matchAll(/#[^#\s]+/g)).map(match => match[0].slice(1).toLowerCase());
|
|
|
|
const termTags = Array.from(rawTerm.matchAll(/#[^#\s]+/g)).map(match => match[0].slice(1).toLowerCase());
|
|
|
|
const termLanguage = rawTerm.match(/lang:(\S+)/)?.[1];
|
|
|
|
const termLanguage = rawTerm.match(/lang:(\S+)/)?.[1];
|
|
|
|
const newRows = communityFullRowCache.filter(
|
|
|
|
/**
|
|
|
|
row => {
|
|
|
|
* @param {{row: HTMLTableRowElement, identifier: string}} rowCache
|
|
|
|
const rowInfo = dom.row_info(row);
|
|
|
|
*/
|
|
|
|
const langAscii = rowInfo.language_flag && flagToLanguageAscii(rowInfo.language_flag).toLowerCase();
|
|
|
|
async function rowMatches(rowCache) {
|
|
|
|
const rowName = rowInfo.name.toLowerCase();
|
|
|
|
const {identifier} = rowCache;
|
|
|
|
const rowDesc = rowInfo.description.toLowerCase();
|
|
|
|
const languageFlag = RoomInfo.getRoomLanguageFlag(identifier);
|
|
|
|
const rowTags = rowInfo.tags.map(({text}) => text.replace(/\s+/g, "-"));
|
|
|
|
const langAscii = languageFlag && flagToLanguageAscii(languageFlag).toLowerCase();
|
|
|
|
if (termLanguage && !langAscii.includes(termLanguage.toLowerCase())) {
|
|
|
|
if (termLanguage && !langAscii.includes(termLanguage.toLowerCase())) {
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (termTags.length >= 1) {
|
|
|
|
const rowName = RoomInfo.getRoomName(identifier).toLowerCase();
|
|
|
|
if (termTags.some(tag => rowTags.some(rowTag => rowTag.startsWith(tag)))) {
|
|
|
|
const rowDesc = RoomInfo.getRoomDescription(identifier).toLowerCase();
|
|
|
|
|
|
|
|
if (rowName.includes(term) || rowDesc.includes(term)) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const rowTags = RoomInfo.getRoomTags(identifier).map(({text}) => text.replace(/\s+/g, "-"));
|
|
|
|
|
|
|
|
for (const termTag of termTags) {
|
|
|
|
|
|
|
|
for (const rowTag of rowTags) {
|
|
|
|
|
|
|
|
if (rowTag.startsWith(termTag)) {
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rowName.includes(term) || rowDesc.includes(term);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
console.time("search");
|
|
|
|
|
|
|
|
const newRowMatches = communityFullRowCache.map(async (rowCache) => ({ rowCache, doesMatch: await rowMatches(rowCache) }));
|
|
|
|
|
|
|
|
const newRows = (await Promise.all(newRowMatches)).filter((row) => row.doesMatch).map(({rowCache}) => rowCache.row);
|
|
|
|
|
|
|
|
console.timeEnd("search");
|
|
|
|
if (newRows.length === 0) {
|
|
|
|
if (newRows.length === 0) {
|
|
|
|
searchBar.classList.add(CLASSES.SEARCH.NO_RESULTS);
|
|
|
|
searchBar.classList.add(CLASSES.SEARCH.NO_RESULTS);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
searchBar.classList.remove(CLASSES.SEARCH.NO_RESULTS);
|
|
|
|
searchBar.classList.remove(CLASSES.SEARCH.NO_RESULTS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
replaceRowsWith(newRows);
|
|
|
|
replaceRowsWith(newRows);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (fillSearchBarWithTerm) {
|
|
|
|
if (fillSearchBarWithTerm) {
|
|
|
|
searchBar.value = rawTerm;
|
|
|
|
searchBar.value = rawTerm;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sortTable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {HTMLTableRowElement[]} rows
|
|
|
|
|
|
|
|
*/
|
|
|
|
function replaceRowsWith(rows) {
|
|
|
|
function replaceRowsWith(rows) {
|
|
|
|
dom.tbl_communities_content_rows().forEach(row => row.remove());
|
|
|
|
const tableBody = dom.tbl_communities()?.querySelector("tbody");
|
|
|
|
dom.tbl_communities().querySelector("tbody").append(...rows);
|
|
|
|
if (!tableBody) throw new Error("Table body missing")
|
|
|
|
|
|
|
|
tableBody.replaceChildren(tableBody.rows[0], ...rows);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
@ -761,21 +787,25 @@ function replaceRowsWith(rows) {
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function sortTable(column) {
|
|
|
|
function sortTable(column) {
|
|
|
|
const table = dom.tbl_communities();
|
|
|
|
const table = dom.tbl_communities();
|
|
|
|
|
|
|
|
if (!table) throw new Error("Table missing");
|
|
|
|
const sortState = getSortState(table);
|
|
|
|
const sortState = getSortState(table);
|
|
|
|
const sortingAsBefore = column === undefined;
|
|
|
|
const sortingAsBefore = column === undefined;
|
|
|
|
|
|
|
|
if (!sortState && !sortingAsBefore) {
|
|
|
|
|
|
|
|
throw new Error("Must supply column on first sort");
|
|
|
|
|
|
|
|
}
|
|
|
|
const sortingNewColumn = column !== sortState?.column;
|
|
|
|
const sortingNewColumn = column !== sortState?.column;
|
|
|
|
const sortedColumn = column ?? sortState?.column;
|
|
|
|
const sortedColumn = column ?? sortState?.column ?? unreachable();
|
|
|
|
const ascending =
|
|
|
|
const ascending =
|
|
|
|
sortingAsBefore ?
|
|
|
|
sortingAsBefore ?
|
|
|
|
sortState.ascending : (
|
|
|
|
sortState?.ascending ?? unreachable() : (
|
|
|
|
sortingNewColumn
|
|
|
|
sortingNewColumn
|
|
|
|
? columnAscendingByDefault(column)
|
|
|
|
? columnAscendingByDefault(column)
|
|
|
|
: !sortState.ascending
|
|
|
|
: sortState?.ascending ?? unreachable()
|
|
|
|
);
|
|
|
|
);
|
|
|
|
const compare = makeRowComparer(sortedColumn, ascending);
|
|
|
|
const compare = makeRowComparer(sortedColumn, ascending);
|
|
|
|
const rows = Array.from(table.rows).slice(1);
|
|
|
|
const rows = dom.tbl_communities_content_rows().map(row => ({row, identifier: row.getAttribute(ATTRIBUTES.ROW.IDENTIFIER)}));
|
|
|
|
rows.sort(compare);
|
|
|
|
rows.sort(compare);
|
|
|
|
replaceRowsWith(rows);
|
|
|
|
replaceRowsWith(rows.map(({row}) => row));
|
|
|
|
setSortState(table, { ascending, column: sortedColumn });
|
|
|
|
setSortState(table, { ascending, column: sortedColumn });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|