235 lines
6.1 KiB
TypeScript
235 lines
6.1 KiB
TypeScript
type Boxes = Set<HTMLInputElement>;
|
|
|
|
let reqBoxes: Boxes;
|
|
let excBoxes: Boxes;
|
|
let allBoxes: Boxes;
|
|
let tags: Map<HTMLElement, string[]>;
|
|
let itemsByYear: Map<string, Set<HTMLElement>>;
|
|
|
|
let showSingles = false;
|
|
|
|
|
|
function fillSets(): [Set<string>, Set<string>] {
|
|
function checkedValues(boxes: Boxes) {
|
|
return new Set([...boxes].filter(b => b.checked).map(b => b.value));
|
|
}
|
|
|
|
return [checkedValues(reqBoxes), checkedValues(excBoxes)];
|
|
}
|
|
|
|
function updateItems() {
|
|
const [reqTags, excTags] = fillSets();
|
|
const anyReq = reqTags.size > 0;
|
|
|
|
for (const [year, items] of itemsByYear) {
|
|
let hideMarker = true;
|
|
|
|
for (const item of items) {
|
|
const req = tags.get(item)?.some(x => reqTags.has(x)) ?? false;
|
|
const exc = tags.get(item)?.some(x => excTags.has(x)) ?? false;
|
|
const hidden = exc || (anyReq && !req);
|
|
|
|
item.hidden = hidden;
|
|
hideMarker &&= hidden;
|
|
}
|
|
|
|
const marker = document.getElementById(`marker-${year}`);
|
|
if (marker !== null) marker.hidden = hideMarker;
|
|
}
|
|
|
|
function disp(pfx: string, tags: Iterable<string>) {
|
|
return [...tags].map(x => pfx + x).join('\u2003'); // em space
|
|
}
|
|
const plus = disp('+\u2009', reqTags); // thin space
|
|
const minus = disp('-\u2009', excTags);
|
|
document.getElementById('filters-details')!.dataset.filters =
|
|
`${plus}\u2003${minus}`.trim();
|
|
}
|
|
|
|
function update() {
|
|
updateItems();
|
|
history.pushState(null, "", makeFragment());
|
|
}
|
|
|
|
function converseId(id: string) {
|
|
if (id.match(/^require/)) {
|
|
return id.replace('require', 'exclude');
|
|
} else {
|
|
return id.replace('exclude', 'require');
|
|
}
|
|
}
|
|
|
|
function toggle(checkbox: HTMLInputElement) {
|
|
if (checkbox.checked) {
|
|
const converse = document.getElementById(converseId(checkbox.id)) as HTMLInputElement;
|
|
converse.checked = false;
|
|
}
|
|
update();
|
|
}
|
|
|
|
|
|
function clearForm() {
|
|
allBoxes.forEach(b => b.checked = b.defaultChecked);
|
|
}
|
|
|
|
function clear(e: Event) {
|
|
clearForm();
|
|
update();
|
|
e.preventDefault();
|
|
}
|
|
|
|
function toggleSingles(e: Event) {
|
|
showSingles = !showSingles;
|
|
|
|
const elems = Array.from(document.querySelectorAll('.filterlist li')) as HTMLElement[];
|
|
for (const li of elems) {
|
|
const countStr = li.querySelector('label')?.dataset.count;
|
|
const count = countStr ? +countStr : 0;
|
|
if (count <= 1 && li instanceof HTMLElement) {
|
|
li.hidden = !showSingles;
|
|
}
|
|
}
|
|
|
|
e.preventDefault();
|
|
}
|
|
|
|
|
|
function makeFragment() {
|
|
const allBoxesArr = Array.from(allBoxes);
|
|
const ids = allBoxesArr.filter(b => b.checked).map(b => b.id);
|
|
if (ids.length == 0) {
|
|
return '#all';
|
|
} else if (allBoxesArr.every(b => b.checked == b.defaultChecked)) {
|
|
return '#';
|
|
} else {
|
|
return '#' + ids.join(';');
|
|
}
|
|
}
|
|
|
|
|
|
type Shortcuts = { [short: string]: Set<string> };
|
|
const shortcuts: Shortcuts = {
|
|
summary: new Set(['require_artsummary']),
|
|
colourexamples: new Set(['require_colourexample']),
|
|
flatexamples: new Set(['require_flatexample']),
|
|
sketchexamples: new Set(['require_sketchexample']),
|
|
iconexamples: new Set(['require_iconexample']),
|
|
curated: new Set(['require_curated']),
|
|
gecs: new Set(['require_niss', 'require_nisse']),
|
|
qt: new Set(['require_q_t_']),
|
|
kesi: new Set(['require_kesi']),
|
|
};
|
|
|
|
function useFragment() {
|
|
const frag = decodeURIComponent(location.hash).replace(/^#/, '');
|
|
const details = document.getElementById('filters-details') as HTMLDetailsElement;
|
|
const fromShortcut = shortcuts[frag];
|
|
|
|
if (!frag) {
|
|
clearForm();
|
|
} else if (frag == 'all') {
|
|
allBoxes.forEach(b => b.checked = false);
|
|
details.open = false;
|
|
} else if (fromShortcut) {
|
|
allBoxes.forEach(b => b.checked = fromShortcut.has(b.id));
|
|
} else {
|
|
const pieces = frag.split(';');
|
|
const set = new Set(pieces);
|
|
const re = /^(require|exclude)_|hide_filters/;
|
|
if (pieces.every(x => re.test(x))) {
|
|
allBoxes.forEach(b => b.checked = set.has(b.id));
|
|
}
|
|
}
|
|
|
|
updateItems();
|
|
}
|
|
|
|
|
|
function sortFilters(cmp: (a: Node, b: Node) => number) {
|
|
function sort1(id: string) {
|
|
const elt = document.getElementById(id);
|
|
if (elt === null) return;
|
|
|
|
const children = Array.from(elt.childNodes);
|
|
children.sort(cmp);
|
|
for (const c of children) {
|
|
elt.removeChild(c);
|
|
elt.appendChild(c);
|
|
}
|
|
}
|
|
|
|
sort1('require');
|
|
sort1('exclude');
|
|
}
|
|
|
|
function sortFiltersAlpha(e: Event) {
|
|
function getName(node: Node): string {
|
|
if (node instanceof Element) {
|
|
return node.getElementsByTagName('input')[0]?.value ?? '';
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
sortFilters((a, b) => getName(a).localeCompare(getName(b)));
|
|
e.preventDefault();
|
|
}
|
|
|
|
function sortFiltersUses(e: Event) {
|
|
function getUses(node: Node): number {
|
|
if (node instanceof Element) {
|
|
const countStr = node.getElementsByTagName('label')[0]?.dataset.count;
|
|
return countStr ? +countStr : 0;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
sortFilters((a, b) => getUses(b) - getUses(a));
|
|
e.preventDefault();
|
|
}
|
|
|
|
|
|
function setup() {
|
|
function inputs(id: string): Boxes {
|
|
const iter = document.getElementById(id)!.getElementsByTagName('input');
|
|
return new Set(Array.from(iter));
|
|
}
|
|
|
|
const items = Array.from(document.getElementsByClassName('post')) as HTMLElement[];
|
|
|
|
itemsByYear = new Map;
|
|
for (const item of items) {
|
|
const year = item.dataset.year;
|
|
if (year !== undefined) {
|
|
if (!itemsByYear.has(year)) {
|
|
itemsByYear.set(year, new Set([item]));
|
|
} else {
|
|
itemsByYear.get(year)?.add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
reqBoxes = inputs('require');
|
|
excBoxes = inputs('exclude');
|
|
allBoxes = new Set([...reqBoxes, ...excBoxes]);
|
|
|
|
tags = new Map(items.map(item => [item, item.dataset.tags?.split(';') ?? []]));
|
|
|
|
|
|
allBoxes.forEach(b => b.addEventListener('change', () => toggle(b)));
|
|
|
|
function addClick(id: string, f: (e: Event) => void) {
|
|
document.getElementById(id)!.addEventListener('click', f);
|
|
}
|
|
addClick('clear', clear);
|
|
addClick('sortalpha', sortFiltersAlpha);
|
|
addClick('sortuses', sortFiltersUses);
|
|
addClick('singles', toggleSingles);
|
|
|
|
window.addEventListener('popstate', useFragment);
|
|
|
|
useFragment();
|
|
}
|
|
|
|
window.addEventListener('DOMContentLoaded', setup);
|
|
|
|
export {};
|