123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- /**
- * Site-wide JS that sets up:
- *
- * [1] MathJax rendering on navigation
- * [2] Sidebar toggling
- * [3] Sidebar scroll preserving
- * [4] Keyboard navigation
- * [5] Right sidebar scroll highlighting / navbar show
- */
- const togglerId = 'js-sidebar-toggle'
- const textbookId = 'js-textbook'
- const togglerActiveClass = 'is-active'
- const textbookActiveClass = 'js-show-sidebar'
- const mathRenderedClass = 'js-mathjax-rendered'
- const icon_path = document.location.origin + `${site_basename}assets`;
- const getToggler = () => document.getElementById(togglerId)
- const getTextbook = () => document.getElementById(textbookId)
- // [1] Run MathJax when Turbolinks navigates to a page.
- // When Turbolinks caches a page, it also saves the MathJax rendering. We mark
- // each page with a CSS class after rendering to prevent double renders when
- // navigating back to a cached page.
- document.addEventListener('turbolinks:load', () => {
- const textbook = getTextbook()
- if (window.MathJax && !textbook.classList.contains(mathRenderedClass)) {
- MathJax.Hub.Queue(['Typeset', MathJax.Hub])
- textbook.classList.add(mathRenderedClass)
- }
- })
- /**
- * [2] Toggles sidebar and menu icon
- */
- const toggleSidebar = () => {
- const toggler = getToggler()
- const textbook = getTextbook()
- if (textbook.classList.contains(textbookActiveClass)) {
- textbook.classList.remove(textbookActiveClass)
- toggler.classList.remove(togglerActiveClass)
- } else {
- textbook.classList.add(textbookActiveClass)
- toggler.classList.add(togglerActiveClass)
- }
- }
- /**
- * Keep the variable below in sync with the tablet breakpoint value in
- * _sass/inuitcss/tools/_tools.mq.scss
- *
- */
- const autoCloseSidebarBreakpoint = 769
- // Set up event listener for sidebar toggle button
- const sidebarButtonHandler = () => {
- getToggler().addEventListener('click', toggleSidebar)
- /**
- * Auto-close sidebar on smaller screens after page load.
- *
- * Having the sidebar be open by default then closing it on page load for
- * small screens gives the illusion that the sidebar closes in response
- * to selecting a page in the sidebar. However, it does cause a bit of jank
- * on the first page load.
- *
- * Since we don't want to persist state in between page navigation, this is
- * the best we can do while optimizing for larger screens where most
- * viewers will read the textbook.
- *
- * The code below assumes that the sidebar is open by default.
- */
- if (window.innerWidth < autoCloseSidebarBreakpoint) toggleSidebar()
- }
- initFunction(sidebarButtonHandler);
- /**
- * [3] Preserve sidebar scroll when navigating between pages
- */
- let sidebarScrollTop = 0
- const getSidebar = () => document.getElementById('js-sidebar')
- document.addEventListener('turbolinks:before-visit', () => {
- sidebarScrollTop = getSidebar().scrollTop
- })
- document.addEventListener('turbolinks:load', () => {
- getSidebar().scrollTop = sidebarScrollTop
- })
- /**
- * Focus textbook page by default so that user can scroll with spacebar
- */
- const focusPage = () => {
- document.querySelector('.c-textbook__page').focus()
- }
- initFunction(focusPage);
- /**
- * [4] Use left and right arrow keys to navigate forward and backwards.
- */
- const LEFT_ARROW_KEYCODE = 37
- const RIGHT_ARROW_KEYCODE = 39
- const getPrevUrl = () => document.getElementById('js-page__nav__prev').href
- const getNextUrl = () => document.getElementById('js-page__nav__next').href
- const initPageNav = (event) => {
- const keycode = event.which
- if (keycode === LEFT_ARROW_KEYCODE) {
- Turbolinks.visit(getPrevUrl())
- } else if (keycode === RIGHT_ARROW_KEYCODE) {
- Turbolinks.visit(getNextUrl())
- }
- };
- var keyboardListener = false;
- const initListener = () => {
- if (keyboardListener === false) {
- document.addEventListener('keydown', initPageNav)
- keyboardListener = true;
- }
- }
- initFunction(initListener);
- /**
- * [5] Scrolling functions:
- * * Right sidebar scroll highlighting
- * * Top navbar hiding for scrolling
- */
- var didScroll;
- initScrollFunc = function() {
- var content = document.querySelector('.c-textbook__page');
- var topbar = document.getElementById("top-navbar");
- var prevScrollpos = content.scrollTop; // Initializing
- scrollFunc = function() {
- // This is the function that does all the stuff when scrolling happens
- var position = content.scrollTop; // Because we use this differently for sidebar
- // Decide to show the navbar
- var currentScrollPos = content.scrollTop;
- var delta = 10;
- var scrollDiff = prevScrollpos - currentScrollPos;
- if (scrollDiff >= delta) {
- // If we scrolled down, consider showing the menu
- topbar.classList.remove("hidetop")
- } else if (Math.abs(scrollDiff) >= delta) {
- // If we scrolled up, consider hiding the menu
- topbar.classList.add("hidetop")
- } else {
- // Do nothing because we didn't scroll enough
- }
- prevScrollpos = currentScrollPos;
- // Highlight the right sidebar section
- position = position + (window.innerHeight / 4); // + Manual offset
- content.querySelectorAll('h2, h3').forEach((header, index) => {
- // Highlight based on location from the top of the screen
- var target = header.getBoundingClientRect().top
- var pixelOffset = 300; // Number of pixels from top to be highlighted
- var id = header.id;
- if (target < pixelOffset) {
- var query = 'ul.toc__menu a[href="#' + id + '"]';
- document.querySelectorAll('ul.toc__menu li').forEach((item) => {item.classList.remove('active')});
- document.querySelectorAll(query).forEach((item) => {item.parentElement.classList.add('active')});
- }
- });
- }
- // Our event listener just sets "yep, I scrolled" to true.
- // The interval function will set it to false after it runs.
- content.addEventListener('scroll', () => {didScroll = true;});
- scrollWait = 250;
- setInterval(() => {
- if (didScroll) {
- scrollFunc();
- didScroll = false;
- }
- }, scrollWait)
- }
- initFunction(initScrollFunc);
|