/**
* @file This file adds filtering options plus a search field in the main menu below the “Glossary” menu item.
* @author Kor Dwarshuis
* @version 1.0.0
* @since 2023-11-03
*/
import overview from '/static/json/overview.json';
import tippy from 'tippy.js';
import 'tippy.js/themes/light-border.css';
const paths = require('../docusaurus.paths.js');
const baseUrl = paths.baseUrl;
const entriesIndex = overview.values[0];
function positionInArray(value) {
for (let i = 0; i < entriesIndex.length; i++) {
if (entriesIndex[i].trim() === value) return i;
}
return -1;
}
function getUniqueValues(data, columnIndex) {
const uniqueValues = new Set();
for (let i = 1; i < data.length; i++) {
const value = data[i][columnIndex];
if (value) {
uniqueValues.add(value);
}
}
return uniqueValues;
}
// overview.values is a 2D array and formColumnNumber, levelColumnNumber, typeColumnNumber are the respective column indexes
const formColumnNumber = positionInArray('Form');
const levelColumnNumber = positionInArray('level');
const typeColumnNumber = positionInArray('Type');
// Get unique values for each column
const uniqueValuesForm = getUniqueValues(overview.values, formColumnNumber);
const uniqueValuesLevel = getUniqueValues(overview.values, levelColumnNumber);
const uniqueValuesType = getUniqueValues(overview.values, typeColumnNumber);
// convert Set to Array
let uniqueValuesArrayForm = Array.from(uniqueValuesForm);
let uniqueValuesArrayLevel = Array.from(uniqueValuesLevel);
let uniqueValuesArrayType = Array.from(uniqueValuesType);
// Create a checkbox state object
function createCheckboxState(uniqueValues) {
const checkboxState = {};
Array.from(uniqueValues).forEach((value) => {
checkboxState[value] = false;
});
return checkboxState;
}
const allFormCheckboxesState = createCheckboxState(uniqueValuesForm);
const allLevelCheckboxesState = createCheckboxState(uniqueValuesLevel);
const allTypeCheckboxesState = createCheckboxState(uniqueValuesType);
function setLocalStorage() {
localStorage.setItem('allFormCheckboxesStateLocalStorage', JSON.stringify(allFormCheckboxesState));
localStorage.setItem('allLevelCheckboxesStateLocalStorage', JSON.stringify(allLevelCheckboxesState));
localStorage.setItem('allTypeCheckboxesStateLocalStorage', JSON.stringify(allTypeCheckboxesState));
}
function getLocalStorage() {
const allFormCheckboxesStateLocalStorage = JSON.parse(localStorage.getItem('allFormCheckboxesStateLocalStorage'));
const allLevelCheckboxesStateLocalStorage = JSON.parse(localStorage.getItem('allLevelCheckboxesStateLocalStorage'));
const allTypeCheckboxesStateLocalStorage = JSON.parse(localStorage.getItem('allTypeCheckboxesStateLocalStorage'));
return [allFormCheckboxesStateLocalStorage, allLevelCheckboxesStateLocalStorage, allTypeCheckboxesStateLocalStorage];
}
function nothingChecked() {
// If only one checkbox is checked, this function returns false, else it returns true. “some” is used to check if any checkbox in each category is checked. If any some call returns true, the function returns false, indicating that not "nothing is checked". If all some calls return false, the function returns true, indicating that "nothing is checked".
const formCheckboxes = document.querySelectorAll('input[type="checkbox"][data-form]');
const levelCheckboxes = document.querySelectorAll('input[type="checkbox"][data-level]');
const typeCheckboxes = document.querySelectorAll('input[type="checkbox"][data-type]');
const isFormChecked = Array.from(formCheckboxes).some(checkbox => checkbox.checked);
const isLevelChecked = Array.from(levelCheckboxes).some(checkbox => checkbox.checked);
const isTypeChecked = Array.from(typeCheckboxes).some(checkbox => checkbox.checked);
return !(isFormChecked || isLevelChecked || isTypeChecked);
}
function areAllCheckboxesFalse() {
const allCheckboxes = [allFormCheckboxesState, allLevelCheckboxesState, allTypeCheckboxesState];
for (const checkboxes of allCheckboxes) {
for (const checkbox in checkboxes) {
if (checkboxes[checkbox]) {
return false;
}
}
}
return true;
}
const addUiToSidebar = () => {
// if the sidebar is not present, do nothing
if (!document.querySelector('.theme-doc-sidebar-container')) {
return;
}
// “a[href=” does not work on iOS…
// const selectorString = ".theme-doc-sidebar-menu li a[href$='" + baseUrl + "docs/glossary']"
// const glossaryMainMenuItem = document.querySelector(selectorString);
//… so we use this instead:
let glossaryMainMenuItem;
const links = document.querySelectorAll(".theme-doc-sidebar-menu li a");
for (let i = 0; i < links.length; i++) {
const link = links[i];
const href = link.getAttribute('href');
const regex = new RegExp(baseUrl + 'docs/category/glossary$');
if (regex.test(href)) {
glossaryMainMenuItem = link;
break;
}
}
// if glossaryMainMenuItem does not exist, do nothing
if (!glossaryMainMenuItem) {
return
}
const parentElement = glossaryMainMenuItem.parentNode.parentNode; // This is the 'li'
const ulElement = parentElement.querySelector('ul');
parentElement.classList.add('glossary-menu');
// if the sidebar is present but the glossary menu is not present, do nothing
if (!ulElement) {
return
}
// now get the ul, so we skip the <a>glossary</a> link
const ulElementChildLinks = ulElement.querySelectorAll('a');
function createFilters() {
// add checkboxes to the sidebar, first add a container
if (document.querySelector('.check-container-form')) {
// checkboxes already added, do nothing
return;
}
const classesToAdd = ['border', 'me-2', 'mb-1', 'ps-2', 'pt-2', 'pe-2', 'rounded', 'd-block', 'bg-light', 'border-secondary'];
const fontSize = '0.8rem';
const checkboxFormContainer = document.createElement('div');
checkboxFormContainer.classList.add(...classesToAdd);
checkboxFormContainer.classList.add('check-container-form');
checkboxFormContainer.style.fontSize = fontSize;
checkboxFormContainer.innerHTML = `<h2 class="d-inline pe-1" style="font-size: 1.2em">Form:</h2><span role="button" class="fs-5 lh-1 explanation-form float-end">?</span>`
const checkboxLevelContainer = document.createElement('div');
checkboxLevelContainer.classList.add(...classesToAdd);
checkboxLevelContainer.classList.add('check-container-level');
checkboxLevelContainer.style.fontSize = fontSize;
checkboxLevelContainer.innerHTML = `<h2 class="d-inline pe-1" style="font-size: 1.2em">Level:</h2><span role="button" class="fs-5 lh-1 explanation-level float-end">?</span>`
const checkboxTypeContainer = document.createElement('div');
checkboxTypeContainer.classList.add(...classesToAdd);
checkboxTypeContainer.classList.add('check-container-type');
checkboxTypeContainer.style.fontSize = fontSize;
checkboxTypeContainer.innerHTML = `<h2 class="d-inline pe-1" style="font-size: 1.2em">Type:</h2><span role="button" class="fs-5 lh-1 explanation-type float-end align-middle">?</span>`
const searchContainer = document.createElement('div');
// searchContainer.classList.add(...classesToAdd);
searchContainer.classList.add('glossary-search-container', 'pe-2');
searchContainer.style.fontSize = fontSize;
searchContainer.innerHTML = `<h2 class="d-none pe-1" style="font-size: 1.2em">Search:</h2>`
// add the container with filters to the sidebar
if (ulElement) {
ulElement.insertBefore(searchContainer, ulElement.firstChild);
ulElement.insertBefore(checkboxLevelContainer, ulElement.firstChild);
ulElement.insertBefore(checkboxFormContainer, ulElement.firstChild);
ulElement.insertBefore(checkboxTypeContainer, ulElement.firstChild);
}
const explanationType = document.querySelector('.explanation-type');
const explanationLevel = document.querySelector('.explanation-level');
const explanationForm = document.querySelector('.explanation-form');
tippy(explanationType, {
triggerTarget: explanationType, // button
trigger: 'click',
arrow: true,
// arrowType: 'round',
theme: 'light-border',
allowHTML: true,
content: `
<div style="font-size: 0.8em">
<h2 class="fs-6 text-center">Type</h2>
<ul class="list-group">
<li class="list-group-item">K: Keri-related</li>
<li class="list-group-item">S: SSI related</li>
<li class="list-group-item">G: General</li>
</ul>
</div
`,
});
tippy(explanationLevel, {
triggerTarget: explanationLevel, // button
trigger: 'click',
arrow: true,
// arrowType: 'round',
theme: 'light-border',
allowHTML: true,
content: `
<div style="font-size: 0.8em">
<h2 class="fs-6 text-center">Level</h2>
<ul class="list-group">
<li class="list-group-item">1: level 1</li>
<li class="list-group-item">2: level 2</li>
<li class="list-group-item">3: level 3</li>
</ul>
</div>
`,
});
tippy(explanationForm, {
triggerTarget: explanationForm, // button
trigger: 'click',
arrow: true,
// arrowType: 'round',
theme: 'light-border',
allowHTML: true,
content: `
<div style="font-size: 0.8em">
<h2 class="fs-6 text-center">Form</h2>
<ul class="list-group">
<li class="list-group-item">n: noun</li>
<li class="list-group-item">r: verb</li>
<li class="list-group-item">v: relation</li>
<li class="list-group-item">a: adjective/adverb</li>
</ul>
</div>
`,
});
const checkboxesForm = [];
const checkboxesLevel = [];
const checkboxesType = [];
// Form checkboxes
for (let i = 0; i < uniqueValuesArrayForm.length; i++) {
const checkboxForm = document.createElement('div');
checkboxForm.classList.add('form-check');
checkboxForm.classList.add('form-check-inline');
checkboxForm.innerHTML = `
<input checked data-filter="form" data-form=${uniqueValuesArrayForm[i]} class="form-check-input" type="checkbox" value="" id="defaultCheck${i}">
<label class="form-check-label" for="defaultCheck${i}">
${uniqueValuesArrayForm[i]}
</label>
`;
checkboxForm.addEventListener('click', (e) => {
checkboxesForm.forEach((cb, index) => {
allFormCheckboxesState[cb.dataset.form] = cb.checked;
});
// update the menu items based on the checkboxData array
setMenuItems(e);
});
const formCheckContainer = document.querySelector('.check-container-form');
formCheckContainer.appendChild(checkboxForm);
checkboxesForm.push(checkboxForm.querySelector('input'));
}
// Level checkboxes
for (let i = 0; i < uniqueValuesArrayLevel.length; i++) {
const checkboxLevel = document.createElement('div');
checkboxLevel.classList.add('form-check');
checkboxLevel.classList.add('form-check-inline');
checkboxLevel.innerHTML = `
<input checked data-filter="level" data-level=${uniqueValuesArrayLevel[i]} class="form-check-input" type="checkbox" value="" id="defaultCheck2${i}">
<label class="level-check-label" for="defaultCheck2${i}">
${uniqueValuesArrayLevel[i]}
</label>
`;
checkboxLevel.addEventListener('click', (e) => {
checkboxesLevel.forEach((cb, index) => {
allLevelCheckboxesState[cb.dataset.level] = cb.checked;
});
// update the menu items based on the checkboxData array
setMenuItems(e);
});
const levelCheckContainer = document.querySelector('.check-container-level');
levelCheckContainer.appendChild(checkboxLevel);
checkboxesLevel.push(checkboxLevel.querySelector('input'));
}
// Type checkboxes
for (let i = 0; i < uniqueValuesArrayType.length; i++) {
const checkboxType = document.createElement('div');
checkboxType.classList.add('form-check');
checkboxType.classList.add('form-check-inline');
checkboxType.innerHTML = `
<input checked data-filter="type" data-type=${uniqueValuesArrayType[i]} class="form-check-input" type="checkbox" value="" id="defaultCheck3${i}">
<label class="type-check-label" for="defaultCheck3${i}">
${uniqueValuesArrayType[i]}
</label>
`;
checkboxType.addEventListener('click', (e) => {
checkboxesType.forEach((cb, index) => {
allTypeCheckboxesState[cb.dataset.type] = cb.checked;
});
// update the menu items based on the checkboxData array
setMenuItems(e);
});
const typeCheckContainer = document.querySelector('.check-container-type');
typeCheckContainer.appendChild(checkboxType);
checkboxesType.push(checkboxType.querySelector('input'));
}
// Search input
const searchInput = document.createElement('input');
searchInput.classList.add('form-control');
searchInput.classList.add('form-control-sm');
searchInput.classList.add('mb-1');
searchInput.classList.add('mt-1');
searchInput.classList.add('border', 'border-secondary');
searchInput.classList.add('search-input');
searchInput.setAttribute('type', 'text');
searchInput.setAttribute('placeholder', 'Search');
searchInput.setAttribute('aria-label', 'Search');
searchInput.setAttribute('aria-describedby', 'search-addon');
// searchInput.setAttribute('autocomplete', 'on');
searchInput.setAttribute('autofocus', 'on');
// empty the search input when the user clicks on it
searchInput.addEventListener('click', (e) => {
e.target.value = '';
removeDisplayNoneAllLinks();
});
searchInput.addEventListener('keyup', (e) => {
const searchValue = e.target.value.toLowerCase();
ulElementChildLinks.forEach((link) => {
if (link.innerText.toLowerCase().includes(searchValue)) {
link.classList.remove('d-none');
} else {
link.classList.add('d-none');
}
});
});
const searchContainerElement = document.querySelector('.glossary-search-container');
searchContainerElement.appendChild(searchInput);
}
createFilters();
document.querySelector('.navbar__toggle').addEventListener('click', (e) => {
createFilters();
});
function greyOutAllLinks() {
ulElementChildLinks.forEach((link) => {
link.classList.add('greyed-out');
});
}
function removeGreyOutAllLinks() {
ulElementChildLinks.forEach((link) => {
link.classList.remove('greyed-out');
});
}
function removeDisplayNoneAllLinks() {
ulElementChildLinks.forEach((link) => {
link.classList.remove('d-none');
});
}
function setMenuItems(e) {
let eTargetChecked = e.target.checked;
let eTargetDatasetFilter = e.target.dataset.filter
// update the checkboxes state object
if (e.target.dataset.form !== undefined) {
allFormCheckboxesState[e.target.dataset.form] = true;
}
if (e.target.dataset.level !== undefined) {
allLevelCheckboxesState[e.target.dataset.level] = true;
}
if (e.target.dataset.type !== undefined) {
allTypeCheckboxesState[e.target.dataset.type] = true;
}
// loop through all links in the glossary menu
ulElementChildLinks.forEach((link) => {
// loop through all entries in the overview.json
for (let i = 0; i < overview.values.length; i++) {
// if the link text matches the term in the overview.json
if (overview.values[i][positionInArray('Term')].trim() === link.innerText) {
// now we can lookup the form, level and type of the entry
// if the checkbox belongs to the “Form” (name of the column in the source data, has nothing to do with html forms) filter
if (eTargetDatasetFilter === "form") {
// if the checkbox value matches the value in the overview.json
if (e.target.dataset.form === overview.values[i][positionInArray('Form')]) {
// then it should control this menu item
// the state of the checkbox determines if the menu item is greyed-out or not
// if the checkbox is checked then remove the greyed-out class
if (eTargetChecked) {
link.classList.remove('greyed-out');
} else {
link.classList.add('greyed-out');
}
}
}
if (eTargetDatasetFilter === "level") {
// if the checkbox value matches the value in the overview.json
if (e.target.dataset.level === overview.values[i][positionInArray('level')]) {
// if the checkbox is checked then remove the greyed-out class
if (eTargetChecked) {
link.classList.remove('greyed-out');
} else {
link.classList.add('greyed-out');
}
}
}
if (eTargetDatasetFilter === "type") {
// if the checkbox value matches the value in the overview.json
if (e.target.dataset.type === overview.values[i][positionInArray('Type')]) {
// if the checkbox is checked then remove the greyed-out class
if (eTargetChecked) {
link.classList.remove('greyed-out');
} else {
link.classList.add('greyed-out');
}
}
}
}
// for (const checkbox in allFormCheckboxesState) {
// if (checkbox === formValue) {
// link.classList.remove('greyed-out');
// } else {
// link.classList.add('greyed-out');
// }
// }
}
});
}
};
// function to call when the route changes
export function onRouteDidUpdate({ location, previousLocation }) {
// Don't execute if we are still on the same page; the lifecycle may be fired
// because the hash changes (e.g. when navigating between headings)
// if (location.pathname === previousLocation?.pathname) return;
addUiToSidebar();
}