initial commit
This commit is contained in:
269
mne/report/js_and_css/report.js
Normal file
269
mne/report/js_and_css/report.js
Normal file
@@ -0,0 +1,269 @@
|
||||
/* We need to refresh the scroll spy after (un)hiding elements */
|
||||
const refreshScrollSpy = () =>{
|
||||
const dataSpyList = [].slice.call(document.querySelectorAll('[data-bs-spy="scroll"]'));
|
||||
dataSpyList.forEach((dataSpyEl) => {
|
||||
bootstrap.ScrollSpy.getInstance(dataSpyEl)
|
||||
.refresh()
|
||||
})
|
||||
}
|
||||
|
||||
const propagateScrollSpyURL = () => {
|
||||
window.addEventListener('activate.bs.scrollspy', (e) => {
|
||||
history.replaceState({}, "", e.relatedTarget);
|
||||
});
|
||||
}
|
||||
|
||||
/* Show or hide elements based on their tag */
|
||||
const toggleTagVisibility = (tagName) => {
|
||||
const tag = tags.find((element) => {
|
||||
return element.name === tagName;
|
||||
});
|
||||
tag.visible = !tag.visible;
|
||||
|
||||
const hiddenTagNames = tags.map((tag) => {
|
||||
if (tag.visible) {
|
||||
return
|
||||
} else {
|
||||
return tag.name
|
||||
}
|
||||
});
|
||||
const elements = $(`[data-mne-tags~="${tagName}"]`);
|
||||
elements.each((i) => {
|
||||
const currentElement = elements[i];
|
||||
const tagValuesOfCurrentElement = currentElement.getAttribute('data-mne-tags');
|
||||
|
||||
// TODO This can probably be refactored to not use a Set.
|
||||
const tagNamesOfCurrentElement = new Set(tagValuesOfCurrentElement.match(/\S+/g)); // non-whitespace
|
||||
const visibleTagNamesOfCurrentElement = new Set(
|
||||
[...tagNamesOfCurrentElement].filter(e => !hiddenTagNames.includes(e))
|
||||
);
|
||||
|
||||
if (visibleTagNamesOfCurrentElement.size === 0) { // hide
|
||||
$(currentElement).slideToggle('fast', () => {
|
||||
currentElement.classList.add('d-none');
|
||||
});
|
||||
} else if ($(currentElement).hasClass('d-none')) { // show
|
||||
currentElement.classList.remove('d-none');
|
||||
$(currentElement).slideToggle('fast');
|
||||
}
|
||||
})
|
||||
|
||||
const tagBadgeElements = document.querySelectorAll(`span.badge[data-mne-tag~="${tagName}"]`);
|
||||
tagBadgeElements.forEach((badgeElement) => {
|
||||
if (tag.visible) {
|
||||
badgeElement.removeAttribute('data-mne-tag-hidden');
|
||||
badgeElement.classList.remove('bg-secondary');
|
||||
badgeElement.classList.add('bg-primary');
|
||||
} else {
|
||||
badgeElement.setAttribute('data-mne-tag-hidden', true);
|
||||
badgeElement.classList.remove('bg-primary');
|
||||
badgeElement.classList.add('bg-secondary');
|
||||
}
|
||||
})
|
||||
|
||||
refreshScrollSpy();
|
||||
}
|
||||
|
||||
/* Gather all available tags and expose them in the global namespace */
|
||||
let tags = []; // array of objects
|
||||
|
||||
const gatherTags = () => {
|
||||
// only consider top-level elements
|
||||
const taggedElements = document.querySelectorAll("#content > div[data-mne-tags]");
|
||||
|
||||
taggedElements.forEach((element) => {
|
||||
const value = element.getAttribute('data-mne-tags');
|
||||
const tagNames = value.match(/\S+/g); // non-whitespace
|
||||
tagNames.forEach((tagName) => {
|
||||
const existingTag = tags.find((element) => {
|
||||
return element.name === tagName;
|
||||
})
|
||||
|
||||
if (existingTag === undefined) {
|
||||
const tag = {
|
||||
name : tagName,
|
||||
visible: true,
|
||||
count: 1
|
||||
};
|
||||
tags.push(tag);
|
||||
} else {
|
||||
existingTag.count = existingTag.count + 1;
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* Badges do display the tag count */
|
||||
const updateTagCountBadges = () => {
|
||||
const menuEntries = document
|
||||
.querySelectorAll("#filter-by-tags-dropdown-menu > ul > li > label[data-mne-tag]")
|
||||
|
||||
menuEntries.forEach((menuEntry) => {
|
||||
const tagName = menuEntry.getAttribute('data-mne-tag');
|
||||
const tag = tags.find((tag) => {
|
||||
return tag.name === tagName;
|
||||
})
|
||||
const tagCount = tag.count;
|
||||
|
||||
const tagCountBadge = menuEntry.querySelector('span.badge');
|
||||
tagCountBadge.innerHTML = tagCount.toString();
|
||||
});
|
||||
}
|
||||
|
||||
const addFilterByTagsCheckboxEventHandlers = () => {
|
||||
// "Filter by tag" checkbox event handling
|
||||
const selectAllTagsCheckboxLabel = document
|
||||
.querySelector('#selectAllTagsCheckboxLabel');
|
||||
const filterByTagsDropdownMenuLabels = document
|
||||
.querySelectorAll("#filter-by-tags-dropdown-menu > ul > li > label[data-mne-tag]")
|
||||
|
||||
filterByTagsDropdownMenuLabels.forEach((label) => {
|
||||
// Prevent dropdown menu from closing when clicking on a tag checkbox label
|
||||
label.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
})
|
||||
|
||||
// Show / hide content if a tag checkbox value has changed
|
||||
const tagName = label.getAttribute("data-mne-tag");
|
||||
const checkbox = label.querySelector("input");
|
||||
checkbox.addEventListener("change", () => {
|
||||
toggleTagVisibility(tagName);
|
||||
})
|
||||
})
|
||||
|
||||
// "Select all" checkbox
|
||||
selectAllTagsCheckboxLabel.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
})
|
||||
const selectAllTagsCheckbox = selectAllTagsCheckboxLabel.querySelector('input');
|
||||
|
||||
selectAllTagsCheckbox.addEventListener("change", (e) => {
|
||||
const selectAllCheckboxStatus = e.target.checked;
|
||||
|
||||
filterByTagsDropdownMenuLabels.forEach((element) => {
|
||||
const checkbox = element.querySelector('input');
|
||||
if (checkbox.checked !== selectAllCheckboxStatus) {
|
||||
checkbox.checked = selectAllCheckboxStatus
|
||||
|
||||
// we need to manually trigger the change event
|
||||
const changeEvent = new Event('change');
|
||||
checkbox.dispatchEvent(changeEvent);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/* Avoid top of content getting hidden behind navbar after clicking on a TOC
|
||||
link */
|
||||
const _handleTocLinkClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const topBarHeight = document.querySelector('#top-bar').scrollHeight
|
||||
const margin = 30 + topBarHeight;
|
||||
|
||||
const tocLinkElement = e.target;
|
||||
const targetDomId = tocLinkElement.getAttribute('href');
|
||||
const targetElement = document.querySelector(targetDomId);
|
||||
const top = targetElement.getBoundingClientRect().top + window.scrollY;
|
||||
|
||||
// Update URL to reflect the current scroll position.
|
||||
// We use history.pushState to change the URL without causing the browser to scroll.
|
||||
history.pushState(null, "", targetDomId);
|
||||
|
||||
// Now scroll to the correct position.
|
||||
window.scrollTo(0, top - margin);
|
||||
}
|
||||
|
||||
const fixScrollingForTocLinks = () => {
|
||||
const tocLinkElements = document.querySelectorAll('#toc-navbar > a');
|
||||
|
||||
tocLinkElements.forEach((element) => {
|
||||
element.removeEventListener('click', _handleTocLinkClick)
|
||||
element.addEventListener('click', _handleTocLinkClick)
|
||||
})
|
||||
}
|
||||
|
||||
const addSliderEventHandlers = () => {
|
||||
const accordionElementsWithSlider = document.querySelectorAll('div.accordion-item.slider');
|
||||
accordionElementsWithSlider.forEach((el) => {
|
||||
const accordionElement = el.querySelector('div.accordion-body');
|
||||
|
||||
const slider = accordionElement.querySelector('input');
|
||||
// const sliderLabel = accordionElement.querySelector('label');
|
||||
const carousel = accordionElement.querySelector('div.carousel');
|
||||
slider.addEventListener('input', (e) => {
|
||||
const sliderValue = parseInt(e.target.value);
|
||||
$(carousel).carousel(sliderValue);
|
||||
})
|
||||
|
||||
// Allow focussing the slider with a click on the slider or carousel, so keyboard
|
||||
// controls (left / right arrow) can be enabled.
|
||||
// This also appears to be the only way to focus the slider in Safari:
|
||||
// https://itnext.io/fixing-focus-for-safari-b5916fef1064?gi=c1b8b043fa9b
|
||||
slider.addEventListener('click', () => {
|
||||
slider.focus({preventScroll: true})
|
||||
})
|
||||
carousel.addEventListener('click', () => {
|
||||
slider.focus({preventScroll: true})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* Avoid top of content gets hidden behind the top navbar */
|
||||
const fixTopMargin = () => {
|
||||
const topBarHeight = document.querySelector('#top-bar').scrollHeight
|
||||
const margin = 30 + topBarHeight;
|
||||
|
||||
document.getElementById('content').style.marginTop = `${margin}px`;
|
||||
document.getElementById('toc').style.marginTop = `${margin}px`;
|
||||
}
|
||||
|
||||
/* Show / hide all tags on keypress */
|
||||
const _globalKeyHandler = (e) => {
|
||||
if (e.code === "KeyT") {
|
||||
const selectAllTagsCheckbox = document
|
||||
.querySelector('#selectAllTagsCheckboxLabel > input');
|
||||
selectAllTagsCheckbox.checked = !selectAllTagsCheckbox.checked;
|
||||
|
||||
// we need to manually trigger the change event
|
||||
const changeEvent = new Event('change');
|
||||
selectAllTagsCheckbox.dispatchEvent(changeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
const enableGlobalKeyHandler = () => {
|
||||
window.onkeydown = (e) => _globalKeyHandler(e);
|
||||
}
|
||||
|
||||
const disableGlobalKeyHandler = () => {
|
||||
window.onkeydown = null;
|
||||
}
|
||||
|
||||
/* Disable processing global key events when a search box is active */
|
||||
const disableGlobalKeysInSearchBox = () => {
|
||||
const searchBoxElements = document.querySelectorAll('input.search-input');
|
||||
searchBoxElements.forEach((el) => {
|
||||
el.addEventListener('focus', () => disableGlobalKeyHandler());
|
||||
el.addEventListener('blur', () => enableGlobalKeyHandler());
|
||||
})
|
||||
}
|
||||
|
||||
/* Run once all content is fully loaded. */
|
||||
window.addEventListener('load', () => {
|
||||
gatherTags();
|
||||
updateTagCountBadges();
|
||||
addFilterByTagsCheckboxEventHandlers();
|
||||
addSliderEventHandlers();
|
||||
fixTopMargin();
|
||||
fixScrollingForTocLinks();
|
||||
hljs.highlightAll(); // enable highlight.js
|
||||
disableGlobalKeysInSearchBox();
|
||||
enableGlobalKeyHandler();
|
||||
propagateScrollSpyURL();
|
||||
});
|
||||
|
||||
/* Resizing the window throws off the scroll spy and top-margin handling. */
|
||||
window.onresize = () => {
|
||||
fixTopMargin();
|
||||
refreshScrollSpy();
|
||||
};
|
||||
Reference in New Issue
Block a user