diff --git a/wowchemy/assets/js/load-theme.js b/wowchemy/assets/js/load-theme.js deleted file mode 100644 index f7bb6f31..00000000 --- a/wowchemy/assets/js/load-theme.js +++ /dev/null @@ -1,47 +0,0 @@ -(function () { - function getThemeMode() { - return parseInt(localStorage.getItem('dark_mode') || 2); - } - - function canChangeTheme() { - // If var is set, then user is allowed to change the theme variation. - return Boolean(window.wcDarkLightEnabled); - } - - function initThemeVariation() { - if (!canChangeTheme()) { - return; - } - - let currentThemeMode = getThemeMode(); - let isDarkTheme; - switch (currentThemeMode) { - case 0: - isDarkTheme = false; - break; - case 1: - isDarkTheme = true; - break; - default: - if (window.matchMedia('(prefers-color-scheme: dark)').matches) { - // The visitor prefers dark themes and switching to the dark variation is allowed by admin. - isDarkTheme = true; - } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { - // The visitor prefers light themes and switching to the dark variation is allowed by admin. - isDarkTheme = false; - } else { - // Use the site's default theme variation based on `light` in the theme file. - isDarkTheme = isSiteThemeDark; - } - break; - } - if (isDarkTheme) { - document.body.classList.add("dark"); - } else { - document.body.classList.remove("dark"); - } - } - - // Initialize theme variation. - initThemeVariation(); -})(); diff --git a/wowchemy/assets/js/wowchemy-animation.js b/wowchemy/assets/js/wowchemy-animation.js new file mode 100644 index 00000000..2aea1e47 --- /dev/null +++ b/wowchemy/assets/js/wowchemy-animation.js @@ -0,0 +1,24 @@ +/************************************************* + * Wowchemy + * https://github.com/wowchemy/wowchemy-hugo-modules + * + * Wowchemy Animation + **************************************************/ + +function fadeIn(element, duration = 600) { + element.style.display = ''; + element.style.opacity = '0'; + let last = +new Date(); + let tick = function() { + element.style.opacity = (+element.style.opacity + (new Date() - last) / duration).toString(); + last = +new Date(); + if (+element.style.opacity < 1) { + (window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16); + } + }; + tick(); +} + +export { + fadeIn, +}; diff --git a/wowchemy/assets/js/wowchemy-init.js b/wowchemy/assets/js/wowchemy-init.js new file mode 100644 index 00000000..8ee498a3 --- /dev/null +++ b/wowchemy/assets/js/wowchemy-init.js @@ -0,0 +1,18 @@ +/************************************************* + * Wowchemy + * https://github.com/wowchemy/wowchemy-hugo-modules + * + * Wowchemy Initialization + **************************************************/ + +import {initThemeVariation} from './wowchemy-theming'; + +import {wcDarkLightEnabled, wcIsSiteThemeDark} from '@params'; + +window.wc = { + darkLightEnabled: wcDarkLightEnabled, + isSiteThemeDark: wcIsSiteThemeDark, +} + +// Initialize theme variation. +initThemeVariation(); diff --git a/wowchemy/assets/js/wowchemy-theming.js b/wowchemy/assets/js/wowchemy-theming.js new file mode 100644 index 00000000..74d761ce --- /dev/null +++ b/wowchemy/assets/js/wowchemy-theming.js @@ -0,0 +1,191 @@ +/************************************************* + * Wowchemy + * https://github.com/wowchemy/wowchemy-hugo-modules + * + * Wowchemy Theming System + * Supported Modes: {0: Day, 1: Night, 2: Auto} + **************************************************/ + +import {fadeIn} from './wowchemy-animation'; + +function getThemeMode() { + return parseInt(localStorage.getItem('dark_mode') || 2); +} + +function canChangeTheme() { + // If var is set, then user is allowed to change the theme variation. + return Boolean(window.wc.darkLightEnabled); +} + +function initThemeVariation() { + if (!canChangeTheme()) { + return; + } + + let currentThemeMode = getThemeMode(); + let isDarkTheme; + switch (currentThemeMode) { + case 0: + isDarkTheme = false; + break; + case 1: + isDarkTheme = true; + break; + default: + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + // The visitor prefers dark themes and switching to the dark variation is allowed by admin. + isDarkTheme = true; + } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { + // The visitor prefers light themes and switching to the dark variation is allowed by admin. + isDarkTheme = false; + } else { + // Use the site's default theme variation based on `light` in the theme file. + isDarkTheme = window.wc.isSiteThemeDark; + } + break; + } + if (isDarkTheme) { + document.body.classList.add("dark"); + } else { + document.body.classList.remove("dark"); + } +} + +function changeThemeModeClick(newMode) { + if (!canChangeTheme()) { + console.info('Cannot set theme - admin disabled theme selector.'); + return; + } + let isDarkTheme; + switch (newMode) { + case 0: + localStorage.setItem('dark_mode', '1'); + isDarkTheme = true; + console.info('User changed theme variation to Dark.'); + showActiveTheme(0); + break; + case 1: + localStorage.setItem('dark_mode', '2'); + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + // The visitor prefers dark themes and switching to the dark variation is allowed by admin. + isDarkTheme = true; + } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { + // The visitor prefers light themes and switching to the dark variation is allowed by admin. + isDarkTheme = false; + } else { + isDarkTheme = window.wc.isSiteThemeDark; // Use the site's default theme variation based on `light` in the theme file. + } + console.info('User changed theme variation to Auto.'); + showActiveTheme(1); + break; + default: + localStorage.setItem('dark_mode', '0'); + isDarkTheme = false; + console.info('User changed theme variation to Light.'); + showActiveTheme(2); + break; + } + renderThemeVariation(isDarkTheme); +} + +function showActiveTheme(mode) { + let linkLight = document.querySelector('.js-set-theme-light'); + let linkDark = document.querySelector('.js-set-theme-dark'); + let linkAuto = document.querySelector('.js-set-theme-auto'); + switch (mode) { + case 0: + // Dark. + linkLight.classList.remove('dropdown-item-active'); + linkDark.classList.add('dropdown-item-active'); + linkAuto.classList.remove('dropdown-item-active'); + break; + case 1: + // Auto. + linkLight.classList.remove('dropdown-item-active'); + linkDark.classList.remove('dropdown-item-active'); + linkAuto.classList.add('dropdown-item-active'); + break; + default: + // Light. + linkLight.classList.add('dropdown-item-active'); + linkDark.classList.remove('dropdown-item-active'); + linkAuto.classList.remove('dropdown-item-active'); + break; + } +} + +/** + * Render theme variation (day or night). + * + * @param {boolean} isDarkTheme + * @param {boolean} init + * @returns {undefined} + */ +function renderThemeVariation(isDarkTheme, init = false) { + // Is code highlighting enabled in site config? + const codeHlLight = document.querySelector('link[title=hl-light]'); + const codeHlDark = document.querySelector('link[title=hl-dark]'); + const codeHlEnabled = codeHlLight || codeHlDark; + const diagramEnabled = document.querySelector('script[title=mermaid]'); + const body = document.body; + + // Check if re-render required. + if (!init) { + // If request to render light when light variation already rendered, return. + // If request to render dark when dark variation already rendered, return. + if ((isDarkTheme === false && !body.classList.contains('dark')) || (isDarkTheme === true && body.classList.contains('dark'))) { + return; + } + } + + if (isDarkTheme === false) { + if (!init) { + // Only fade in the page when changing the theme variation. + //$('body').css({opacity: 0, visibility: 'visible'}).animate({opacity: 1}, 500); + Object.assign(document.body.style, {opacity: 0, visibility: 'visible'}); + fadeIn(document.body, 600); + } + body.classList.remove('dark'); + if (codeHlEnabled) { + codeHlLight.disabled = false; + codeHlDark.disabled = true; + } + if (diagramEnabled) { + if (init) { + /** @namespace window.mermaid **/ + window.mermaid.initialize({theme: 'default', securityLevel: 'loose'}); + } else { + // Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks. + location.reload(); + } + } + } else if (isDarkTheme === true) { + if (!init) { + // Only fade in the page when changing the theme variation. + Object.assign(document.body.style, {opacity: 0, visibility: 'visible'}); + fadeIn(document.body, 600); + } + body.classList.add("dark"); + if (codeHlEnabled) { + codeHlLight.disabled = true; + codeHlDark.disabled = false; + } + if (diagramEnabled) { + if (init) { + /** @namespace window.mermaid **/ + window.mermaid.initialize({theme: 'dark', securityLevel: 'loose'}); + } else { + // Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks. + location.reload(); + } + } + } +} + + +export { + canChangeTheme, + initThemeVariation, + changeThemeModeClick, + renderThemeVariation, +}; diff --git a/wowchemy/assets/js/wowchemy.js b/wowchemy/assets/js/wowchemy.js index 17960038..80969f22 100644 --- a/wowchemy/assets/js/wowchemy.js +++ b/wowchemy/assets/js/wowchemy.js @@ -5,791 +5,600 @@ * Core JS functions and initialization. **************************************************/ -(function ($) { +import {canChangeTheme, changeThemeModeClick, initThemeVariation, renderThemeVariation} from './wowchemy-theming'; - /* --------------------------------------------------------------------------- - * Responsive scrolling for URL hashes. - * --------------------------------------------------------------------------- */ - // Dynamically get responsive navigation bar height for offsetting Scrollspy. - function getNavBarHeight() { - let $navbar = $('#navbar-main'); - let navbar_offset = $navbar.outerHeight(); - console.debug('Navbar height: ' + navbar_offset); - return navbar_offset; - } +/* --------------------------------------------------------------------------- + * Responsive scrolling for URL hashes. + * --------------------------------------------------------------------------- */ - /** - * Responsive hash scrolling. - * Check for a URL hash as an anchor. - * If it exists on current page, scroll to it responsively. - * If `target` argument omitted (e.g. after event), assume it's the window's hash. - */ - function scrollToAnchor(target) { - // If `target` is undefined or HashChangeEvent object, set it to window's hash. - // Decode the hash as browsers can encode non-ASCII characters (e.g. Chinese symbols). - target = (typeof target === 'undefined' || typeof target === 'object') ? decodeURIComponent(window.location.hash) : target; +// Dynamically get responsive navigation bar height for offsetting Scrollspy. +function getNavBarHeight() { + let $navbar = $('#navbar-main'); + let navbar_offset = $navbar.outerHeight(); + console.debug('Navbar height: ' + navbar_offset); + return navbar_offset; +} - // If target element exists, scroll to it taking into account fixed navigation bar offset. - if ($(target).length) { - // Escape special chars from IDs, such as colons found in Markdown footnote links. - target = '#' + $.escapeSelector(target.substring(1)); // Previously, `target = target.replace(/:/g, '\\:');` +/** + * Responsive hash scrolling. + * Check for a URL hash as an anchor. + * If it exists on current page, scroll to it responsively. + * If `target` argument omitted (e.g. after event), assume it's the window's hash. + */ +function scrollToAnchor(target) { + // If `target` is undefined or HashChangeEvent object, set it to window's hash. + // Decode the hash as browsers can encode non-ASCII characters (e.g. Chinese symbols). + target = (typeof target === 'undefined' || typeof target === 'object') ? decodeURIComponent(window.location.hash) : target; - let elementOffset = Math.ceil($(target).offset().top - getNavBarHeight()); // Round up to highlight right ID! - $('body').addClass('scrolling'); - $('html, body').animate({ - scrollTop: elementOffset - }, 600, function () { - $('body').removeClass('scrolling'); - }); - } else { - console.debug('Cannot scroll to target `#' + target + '`. ID not found!'); - } - } + // If target element exists, scroll to it taking into account fixed navigation bar offset. + if ($(target).length) { + // Escape special chars from IDs, such as colons found in Markdown footnote links. + target = '#' + $.escapeSelector(target.substring(1)); // Previously, `target = target.replace(/:/g, '\\:');` - // Make Scrollspy responsive. - function fixScrollspy() { - let $body = $('body'); - let data = $body.data('bs.scrollspy'); - if (data) { - data._config.offset = getNavBarHeight(); - $body.data('bs.scrollspy', data); - $body.scrollspy('refresh'); - } - } - - function removeQueryParamsFromUrl() { - if (window.history.replaceState) { - let urlWithoutSearchParams = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.hash; - window.history.replaceState({path: urlWithoutSearchParams}, '', urlWithoutSearchParams); - } - } - - // Check for hash change event and fix responsive offset for hash links (e.g. Markdown footnotes). - window.addEventListener("hashchange", scrollToAnchor); - - /* --------------------------------------------------------------------------- - * Add smooth scrolling to all links inside the main navbar. - * --------------------------------------------------------------------------- */ - - $('#navbar-main li.nav-item a.nav-link, .js-scroll').on('click', function (event) { - // Store requested URL hash. - let hash = this.hash; - - // If we are on a widget page and the navbar link is to a section on the same page. - if (this.pathname === window.location.pathname && hash && $(hash).length && ($(".js-widget-page").length > 0)) { - // Prevent default click behavior. - event.preventDefault(); - - // Use jQuery's animate() method for smooth page scrolling. - // The numerical parameter specifies the time (ms) taken to scroll to the specified hash. - let elementOffset = Math.ceil($(hash).offset().top - getNavBarHeight()); // Round up to highlight right ID! - - // Uncomment to debug. - // let scrollTop = $(window).scrollTop(); - // let scrollDelta = (elementOffset - scrollTop); - // console.debug('Scroll Delta: ' + scrollDelta); - - $('html, body').animate({ - scrollTop: elementOffset - }, 800); - } - }); - - /* --------------------------------------------------------------------------- - * Hide mobile collapsable menu on clicking a link. - * --------------------------------------------------------------------------- */ - - $(document).on('click', '.navbar-collapse.show', function (e) { - //get the element that was clicked, even if the element that is inside the element is e.target - let targetElement = $(e.target).is('a') ? $(e.target) : $(e.target).parent(); - - if (targetElement.is('a') && targetElement.attr('class') != 'dropdown-toggle') { - $(this).collapse('hide'); - } - }); - - /* --------------------------------------------------------------------------- - * Filter publications. - * --------------------------------------------------------------------------- */ - - // Active publication filters. - let pubFilters = {}; - - // Search term. - let searchRegex; - - // Filter values (concatenated). - let filterValues; - - // Publication container. - let $grid_pubs = $('#container-publications'); - - // Initialise Isotope publication layout if required. - if ($grid_pubs.length) - { - $grid_pubs.isotope({ - itemSelector: '.isotope-item', - percentPosition: true, - masonry: { - // Use Bootstrap compatible grid layout. - columnWidth: '.grid-sizer' - }, - filter: function () { - let $this = $(this); - let searchResults = searchRegex ? $this.text().match(searchRegex) : true; - let filterResults = filterValues ? $this.is(filterValues) : true; - return searchResults && filterResults; - } + let elementOffset = Math.ceil($(target).offset().top - getNavBarHeight()); // Round up to highlight right ID! + $('body').addClass('scrolling'); + $('html, body').animate({ + scrollTop: elementOffset + }, 600, function () { + $('body').removeClass('scrolling'); }); + } else { + console.debug('Cannot scroll to target `#' + target + '`. ID not found!'); + } +} - // Filter by search term. - let $quickSearch = $('.filter-search').keyup(debounce(function () { - searchRegex = new RegExp($quickSearch.val(), 'gi'); - $grid_pubs.isotope(); - })); +// Make Scrollspy responsive. +function fixScrollspy() { + let $body = $('body'); + let data = $body.data('bs.scrollspy'); + if (data) { + data._config.offset = getNavBarHeight(); + $body.data('bs.scrollspy', data); + $body.scrollspy('refresh'); + } +} - $('.pub-filters').on('change', function () { +function removeQueryParamsFromUrl() { + if (window.history.replaceState) { + let urlWithoutSearchParams = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.hash; + window.history.replaceState({path: urlWithoutSearchParams}, '', urlWithoutSearchParams); + } +} + +// Check for hash change event and fix responsive offset for hash links (e.g. Markdown footnotes). +window.addEventListener("hashchange", scrollToAnchor); + +/* --------------------------------------------------------------------------- + * Add smooth scrolling to all links inside the main navbar. + * --------------------------------------------------------------------------- */ + +$('#navbar-main li.nav-item a.nav-link, .js-scroll').on('click', function (event) { + // Store requested URL hash. + let hash = this.hash; + + // If we are on a widget page and the navbar link is to a section on the same page. + if (this.pathname === window.location.pathname && hash && $(hash).length && ($(".js-widget-page").length > 0)) { + // Prevent default click behavior. + event.preventDefault(); + + // Use jQuery's animate() method for smooth page scrolling. + // The numerical parameter specifies the time (ms) taken to scroll to the specified hash. + let elementOffset = Math.ceil($(hash).offset().top - getNavBarHeight()); // Round up to highlight right ID! + + // Uncomment to debug. + // let scrollTop = $(window).scrollTop(); + // let scrollDelta = (elementOffset - scrollTop); + // console.debug('Scroll Delta: ' + scrollDelta); + + $('html, body').animate({ + scrollTop: elementOffset + }, 800); + } +}); + +/* --------------------------------------------------------------------------- + * Hide mobile collapsable menu on clicking a link. + * --------------------------------------------------------------------------- */ + +$(document).on('click', '.navbar-collapse.show', function (e) { + //get the element that was clicked, even if the element that is inside the element is e.target + let targetElement = $(e.target).is('a') ? $(e.target) : $(e.target).parent(); + + if (targetElement.is('a') && targetElement.attr('class') != 'dropdown-toggle') { + $(this).collapse('hide'); + } +}); + +/* --------------------------------------------------------------------------- + * Filter publications. + * --------------------------------------------------------------------------- */ + +// Active publication filters. +let pubFilters = {}; + +// Search term. +let searchRegex; + +// Filter values (concatenated). +let filterValues; + +// Publication container. +let $grid_pubs = $('#container-publications'); + +// Initialise Isotope publication layout if required. +if ($grid_pubs.length) { + $grid_pubs.isotope({ + itemSelector: '.isotope-item', + percentPosition: true, + masonry: { + // Use Bootstrap compatible grid layout. + columnWidth: '.grid-sizer' + }, + filter: function () { let $this = $(this); - - // Get group key. - let filterGroup = $this[0].getAttribute('data-filter-group'); - - // Set filter for group. - pubFilters[filterGroup] = this.value; - - // Combine filters. - filterValues = concatValues(pubFilters); - - // Activate filters. - $grid_pubs.isotope(); - - // If filtering by publication type, update the URL hash to enable direct linking to results. - if (filterGroup === "pubtype") { - // Set hash URL to current filter. - let url = $(this).val(); - if (url.substr(0, 9) === '.pubtype-') { - window.location.hash = url.substr(9); - } else { - window.location.hash = ''; - } - } - }); - } - - // Debounce input to prevent spamming filter requests. - function debounce(fn, threshold) { - let timeout; - threshold = threshold || 100; - return function debounced() { - clearTimeout(timeout); - let args = arguments; - let _this = this; - - function delayed() { - fn.apply(_this, args); - } - - timeout = setTimeout(delayed, threshold); - }; - } - - // Flatten object by concatenating values. - function concatValues(obj) { - let value = ''; - for (let prop in obj) { - value += obj[prop]; + let searchResults = searchRegex ? $this.text().match(searchRegex) : true; + let filterResults = filterValues ? $this.is(filterValues) : true; + return searchResults && filterResults; } - return value; - } + }); - // Filter publications according to hash in URL. - function filter_publications() { - // Check for Isotope publication layout. - if (!$grid_pubs.length) - return + // Filter by search term. + let $quickSearch = $('.filter-search').keyup(debounce(function () { + searchRegex = new RegExp($quickSearch.val(), 'gi'); + $grid_pubs.isotope(); + })); - let urlHash = window.location.hash.replace('#', ''); - let filterValue = '*'; + $('.pub-filters').on('change', function () { + let $this = $(this); - // Check if hash is numeric. - if (urlHash != '' && !isNaN(urlHash)) { - filterValue = '.pubtype-' + urlHash; - } + // Get group key. + let filterGroup = $this[0].getAttribute('data-filter-group'); - // Set filter. - let filterGroup = 'pubtype'; - pubFilters[filterGroup] = filterValue; + // Set filter for group. + pubFilters[filterGroup] = this.value; + + // Combine filters. filterValues = concatValues(pubFilters); // Activate filters. $grid_pubs.isotope(); - // Set selected option. - $('.pubtype-select').val(filterValue); - } - - /* --------------------------------------------------------------------------- - * Google Maps or OpenStreetMap via Leaflet. - * --------------------------------------------------------------------------- */ - - function initMap() { - if ($('#map').length) { - let map_provider = $('#map-provider').val(); - let lat = $('#map-lat').val(); - let lng = $('#map-lng').val(); - let zoom = parseInt($('#map-zoom').val()); - let address = $('#map-dir').val(); - let api_key = $('#map-api-key').val(); - - if (map_provider == 1) { - let map = new GMaps({ - div: '#map', - lat: lat, - lng: lng, - zoom: zoom, - zoomControl: true, - zoomControlOpt: { - style: 'SMALL', - position: 'TOP_LEFT' - }, - panControl: false, - streetViewControl: false, - mapTypeControl: false, - overviewMapControl: false, - scrollwheel: true, - draggable: true - }); - - map.addMarker({ - lat: lat, - lng: lng, - click: function (e) { - let url = 'https://www.google.com/maps/place/' + encodeURIComponent(address) + '/@' + lat + ',' + lng + '/'; - window.open(url, '_blank') - }, - title: address - }) + // If filtering by publication type, update the URL hash to enable direct linking to results. + if (filterGroup === "pubtype") { + // Set hash URL to current filter. + let url = $(this).val(); + if (url.substr(0, 9) === '.pubtype-') { + window.location.hash = url.substr(9); } else { - let map = new L.map('map').setView([lat, lng], zoom); - if (map_provider == 3 && api_key.length) { - L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', { - attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox', - tileSize: 512, - maxZoom: 18, - zoomOffset: -1, - id: 'mapbox/streets-v11', - accessToken: api_key - }).addTo(map); - } else { - L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - maxZoom: 19, - attribution: '© OpenStreetMap' - }).addTo(map); - } - let marker = L.marker([lat, lng]).addTo(map); - let url = lat + ',' + lng + '#map=' + zoom + '/' + lat + '/' + lng + '&layers=N'; - marker.bindPopup(address + '

Routing via OpenStreetMap

'); + window.location.hash = ''; } } + }); +} + +// Debounce input to prevent spamming filter requests. +function debounce(fn, threshold) { + let timeout; + threshold = threshold || 100; + return function debounced() { + clearTimeout(timeout); + let args = arguments; + let _this = this; + + function delayed() { + fn.apply(_this, args); + } + + timeout = setTimeout(delayed, threshold); + }; +} + +// Flatten object by concatenating values. +function concatValues(obj) { + let value = ''; + for (let prop in obj) { + value += obj[prop]; + } + return value; +} + +// Filter publications according to hash in URL. +function filter_publications() { + // Check for Isotope publication layout. + if (!$grid_pubs.length) + return + + let urlHash = window.location.hash.replace('#', ''); + let filterValue = '*'; + + // Check if hash is numeric. + if (urlHash != '' && !isNaN(urlHash)) { + filterValue = '.pubtype-' + urlHash; } - /* --------------------------------------------------------------------------- - * GitHub API. - * --------------------------------------------------------------------------- */ + // Set filter. + let filterGroup = 'pubtype'; + pubFilters[filterGroup] = filterValue; + filterValues = concatValues(pubFilters); - function printLatestRelease(selector, repo) { - $.getJSON('https://api.github.com/repos/' + repo + '/tags').done(function (json) { - let release = json[0]; - $(selector).append(' ' + release.name); - }).fail(function (jqxhr, textStatus, error) { - let err = textStatus + ", " + error; - console.log("Request Failed: " + err); - }); - } + // Activate filters. + $grid_pubs.isotope(); - /* --------------------------------------------------------------------------- - * Toggle search dialog. - * --------------------------------------------------------------------------- */ + // Set selected option. + $('.pubtype-select').val(filterValue); +} - function toggleSearchDialog() { - if ($('body').hasClass('searching')) { - // Clear search query and hide search modal. - $('[id=search-query]').blur(); - $('body').removeClass('searching compensate-for-scrollbar'); +/* --------------------------------------------------------------------------- +* Google Maps or OpenStreetMap via Leaflet. +* --------------------------------------------------------------------------- */ - // Remove search query params from URL as user has finished searching. - removeQueryParamsFromUrl(); +function initMap() { + if ($('#map').length) { + let map_provider = $('#map-provider').val(); + let lat = $('#map-lat').val(); + let lng = $('#map-lng').val(); + let zoom = parseInt($('#map-zoom').val()); + let address = $('#map-dir').val(); + let api_key = $('#map-api-key').val(); - // Prevent fixed positioned elements (e.g. navbar) moving due to scrollbars. - $('#fancybox-style-noscroll').remove(); + if (map_provider == 1) { + let map = new GMaps({ + div: '#map', + lat: lat, + lng: lng, + zoom: zoom, + zoomControl: true, + zoomControlOpt: { + style: 'SMALL', + position: 'TOP_LEFT' + }, + panControl: false, + streetViewControl: false, + mapTypeControl: false, + overviewMapControl: false, + scrollwheel: true, + draggable: true + }); + + map.addMarker({ + lat: lat, + lng: lng, + click: function (e) { + let url = 'https://www.google.com/maps/place/' + encodeURIComponent(address) + '/@' + lat + ',' + lng + '/'; + window.open(url, '_blank') + }, + title: address + }) } else { - // Prevent fixed positioned elements (e.g. navbar) moving due to scrollbars. - if (!$('#fancybox-style-noscroll').length && document.body.scrollHeight > window.innerHeight) { - $('head').append( - '' - ); - $('body').addClass('compensate-for-scrollbar'); + let map = new L.map('map').setView([lat, lng], zoom); + if (map_provider == 3 && api_key.length) { + L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', { + attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox', + tileSize: 512, + maxZoom: 18, + zoomOffset: -1, + id: 'mapbox/streets-v11', + accessToken: api_key + }).addTo(map); + } else { + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' + }).addTo(map); } - - // Show search modal. - $('body').addClass('searching'); - $('.search-results').css({opacity: 0, visibility: 'visible'}).animate({opacity: 1}, 200); - $('#search-query').focus(); + let marker = L.marker([lat, lng]).addTo(map); + let url = lat + ',' + lng + '#map=' + zoom + '/' + lat + '/' + lng + '&layers=N'; + marker.bindPopup(address + '

Routing via OpenStreetMap

'); } } +} - /* --------------------------------------------------------------------------- - * Change Theme Mode (0: Day, 1: Night, 2: Auto). - * --------------------------------------------------------------------------- */ - - // TODO: import theme functions from load-theme.js to avoid duplication. - function canChangeTheme() { - // If var is set, then user is allowed to change the theme variation. - return Boolean(window.wcDarkLightEnabled); - } - - function getThemeMode() { - return parseInt(localStorage.getItem('dark_mode') || 2); - } - - function changeThemeModeClick(newMode) { - console.info('Request to set theme.'); - if (!canChangeTheme()) { - console.info('Cannot set theme - admin disabled theme selector.'); - return; - } - let isDarkTheme; - switch (newMode) { - case 0: - localStorage.setItem('dark_mode', '1'); - isDarkTheme = true; - console.info('User changed theme variation to Dark.'); - showActiveTheme(0); - break; - case 1: - localStorage.setItem('dark_mode', '2'); - if (window.matchMedia('(prefers-color-scheme: dark)').matches) { - // The visitor prefers dark themes and switching to the dark variation is allowed by admin. - isDarkTheme = true; - } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { - // The visitor prefers light themes and switching to the dark variation is allowed by admin. - isDarkTheme = false; - } else { - isDarkTheme = isSiteThemeDark; // Use the site's default theme variation based on `light` in the theme file. - } - console.info('User changed theme variation to Auto.'); - showActiveTheme(1); - break; - default: - localStorage.setItem('dark_mode', '0'); - isDarkTheme = false; - console.info('User changed theme variation to Light.'); - showActiveTheme(2); - break; - } - renderThemeVariation(isDarkTheme); - } - - function showActiveTheme(mode){ - switch (mode) { - case 0: - // Dark. - $('.js-set-theme-light').removeClass('dropdown-item-active'); - $('.js-set-theme-dark').addClass('dropdown-item-active'); - $('.js-set-theme-auto').removeClass('dropdown-item-active'); - break; - case 1: - // Auto. - $('.js-set-theme-light').removeClass('dropdown-item-active'); - $('.js-set-theme-dark').removeClass('dropdown-item-active'); - $('.js-set-theme-auto').addClass('dropdown-item-active'); - break; - default: - // Light. - $('.js-set-theme-light').addClass('dropdown-item-active'); - $('.js-set-theme-dark').removeClass('dropdown-item-active'); - $('.js-set-theme-auto').removeClass('dropdown-item-active'); - break; - } - } - - function getThemeVariation() { - if (!canChangeTheme()) { - return isSiteThemeDark; // Use the site's default theme variation based on `light` in the theme file. - } - let currentThemeMode = getThemeMode(); - let isDarkTheme; - switch (currentThemeMode) { - case 0: - isDarkTheme = false; - break; - case 1: - isDarkTheme = true; - break; - default: - if (window.matchMedia('(prefers-color-scheme: dark)').matches) { - // The visitor prefers dark themes and switching to the dark variation is allowed by admin. - isDarkTheme = true; - } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { - // The visitor prefers light themes and switching to the dark variation is allowed by admin. - isDarkTheme = false; - } else { - isDarkTheme = isSiteThemeDark; // Use the site's default theme variation based on `light` in the theme file. - } - break; - } - return isDarkTheme; - } - - /** - * Render theme variation (day or night). - * - * @param {boolean} isDarkTheme - * @param {boolean} init - * @returns {undefined} - */ - function renderThemeVariation(isDarkTheme, init = false) { - // Is code highlighting enabled in site config? - const codeHlEnabled = $('link[title=hl-light]').length > 0; - const codeHlLight = $('link[title=hl-light]')[0]; - const codeHlDark = $('link[title=hl-dark]')[0]; - const diagramEnabled = $('script[title=mermaid]').length > 0; - - // Check if re-render required. - if (!init) { - // If request to render light when light variation already rendered, return. - // If request to render dark when dark variation already rendered, return. - if ((isDarkTheme === false && !$('body').hasClass('dark')) || (isDarkTheme === true && $('body').hasClass('dark'))) { - return; - } - } - - if (isDarkTheme === false) { - if (!init) { - // Only fade in the page when changing the theme variation. - $('body').css({opacity: 0, visibility: 'visible'}).animate({opacity: 1}, 500); - } - $('body').removeClass('dark'); - if (codeHlEnabled) { - codeHlLight.disabled = false; - codeHlDark.disabled = true; - } - if (diagramEnabled) { - if (init) { - mermaid.initialize({theme: 'default', securityLevel: 'loose'}); - } else { - // Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks. - location.reload(); - } - } - } else if (isDarkTheme === true) { - if (!init) { - // Only fade in the page when changing the theme variation. - $('body').css({opacity: 0, visibility: 'visible'}).animate({opacity: 1}, 500); - } - $('body').addClass('dark'); - if (codeHlEnabled) { - codeHlLight.disabled = true; - codeHlDark.disabled = false; - } - if (diagramEnabled) { - if (init) { - mermaid.initialize({theme: 'dark', securityLevel: 'loose'}); - } else { - // Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks. - location.reload(); - } - } - } - } - - function initThemeVariation() { - // If theme changer component present, set its icon according to the theme mode (day, night, or auto). - if (canChangeTheme) { - let themeMode = getThemeMode(); - switch (themeMode) { - case 0: - showActiveTheme(2); - console.info('Initialize theme variation to Light.'); - break; - case 1: - showActiveTheme(0); - console.info('Initialize theme variation to Dark.'); - break; - default: - showActiveTheme(1); - console.info('Initialize theme variation to Auto.'); - break; - } - } - // Render the day or night theme. - let isDarkTheme = getThemeVariation(); - renderThemeVariation(isDarkTheme, true); - } - - /* --------------------------------------------------------------------------- - * Normalize Bootstrap Carousel Slide Heights. - * --------------------------------------------------------------------------- */ - - function normalizeCarouselSlideHeights() { - $('.carousel').each(function () { - // Get carousel slides. - let items = $('.carousel-item', this); - // Reset all slide heights. - items.css('min-height', 0); - // Normalize all slide heights. - let maxHeight = Math.max.apply(null, items.map(function () { - return $(this).outerHeight() - }).get()); - items.css('min-height', maxHeight + 'px'); - }) - } - - /* --------------------------------------------------------------------------- - * Fix Hugo's Goldmark output and Mermaid code blocks. +/* --------------------------------------------------------------------------- + * GitHub API. * --------------------------------------------------------------------------- */ - /** - * Fix Hugo's Goldmark output. - */ - function fixHugoOutput() { - // Fix Goldmark table of contents. - // - Must be performed prior to initializing ScrollSpy. - $('#TableOfContents').addClass('nav flex-column'); - $('#TableOfContents li').addClass('nav-item'); - $('#TableOfContents li a').addClass('nav-link'); +function printLatestRelease(selector, repo) { + $.getJSON('https://api.github.com/repos/' + repo + '/tags').done(function (json) { + let release = json[0]; + $(selector).append(' ' + release.name); + }).fail(function (jqxhr, textStatus, error) { + let err = textStatus + ", " + error; + console.log("Request Failed: " + err); + }); +} - // Fix Goldmark task lists (remove bullet points). - $("input[type='checkbox'][disabled]").parents('ul').addClass('task-list'); - } +/* --------------------------------------------------------------------------- +* Toggle search dialog. +* --------------------------------------------------------------------------- */ - /** - * Fix Mermaid.js clash with Highlight.js. - * Refactor Mermaid code blocks as divs to prevent Highlight parsing them and enable Mermaid to parse them. - */ - function fixMermaid() { - let mermaids = []; - [].push.apply(mermaids, document.getElementsByClassName('language-mermaid')); - for (let i = 0; i < mermaids.length; i++) { - $(mermaids[i]).unwrap('pre'); // Remove
 wrapper.
-      $(mermaids[i]).replaceWith(function () {
-        // Convert  block to 
and add `mermaid` class so that Mermaid will parse it. - return $("
").append($(this).contents()).addClass('mermaid'); - }); - } - } +function toggleSearchDialog() { + if ($('body').hasClass('searching')) { + // Clear search query and hide search modal. + $('[id=search-query]').blur(); + $('body').removeClass('searching compensate-for-scrollbar'); - /* --------------------------------------------------------------------------- - * On document ready. - * --------------------------------------------------------------------------- */ + // Remove search query params from URL as user has finished searching. + removeQueryParamsFromUrl(); - $(document).ready(function () { - fixHugoOutput(); - fixMermaid(); - - // Initialise code highlighting if enabled for this page. - // Note: this block should be processed after the Mermaid code-->div conversion. - if (code_highlighting) { - hljs.initHighlighting(); + // Prevent fixed positioned elements (e.g. navbar) moving due to scrollbars. + $('#fancybox-style-noscroll').remove(); + } else { + // Prevent fixed positioned elements (e.g. navbar) moving due to scrollbars. + if (!$('#fancybox-style-noscroll').length && document.body.scrollHeight > window.innerHeight) { + $('head').append( + '' + ); + $('body').addClass('compensate-for-scrollbar'); } - // Initialize theme variation. - initThemeVariation(); + // Show search modal. + $('body').addClass('searching'); + $('.search-results').css({opacity: 0, visibility: 'visible'}).animate({opacity: 1}, 200); + $('#search-query').focus(); + } +} - // Change theme mode. - $('.js-set-theme-light').click(function (e) { - e.preventDefault(); - changeThemeModeClick(2); - }); - $('.js-set-theme-dark').click(function (e) { - e.preventDefault(); - changeThemeModeClick(0); - }); - $('.js-set-theme-auto').click(function (e) { - e.preventDefault(); - changeThemeModeClick(1); - }); +/* --------------------------------------------------------------------------- +* Normalize Bootstrap Carousel Slide Heights. +* --------------------------------------------------------------------------- */ - // Live update of day/night mode on system preferences update (no refresh required). - // Note: since we listen only for *dark* events, we won't detect other scheme changes such as light to no-preference. - const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - darkModeMediaQuery.addListener((e) => { - if (!canChangeTheme()) { - // Changing theme variation is not allowed by admin. - return; +function normalizeCarouselSlideHeights() { + $('.carousel').each(function () { + // Get carousel slides. + let items = $('.carousel-item', this); + // Reset all slide heights. + items.css('min-height', 0); + // Normalize all slide heights. + let maxHeight = Math.max.apply(null, items.map(function () { + return $(this).outerHeight() + }).get()); + items.css('min-height', maxHeight + 'px'); + }) +} + +/* --------------------------------------------------------------------------- +* Fix Hugo's Goldmark output and Mermaid code blocks. +* --------------------------------------------------------------------------- */ + +/** + * Fix Hugo's Goldmark output. + */ +function fixHugoOutput() { + // Fix Goldmark table of contents. + // - Must be performed prior to initializing ScrollSpy. + $('#TableOfContents').addClass('nav flex-column'); + $('#TableOfContents li').addClass('nav-item'); + $('#TableOfContents li a').addClass('nav-link'); + + // Fix Goldmark task lists (remove bullet points). + $("input[type='checkbox'][disabled]").parents('ul').addClass('task-list'); +} + +/** + * Fix Mermaid.js clash with Highlight.js. + * Refactor Mermaid code blocks as divs to prevent Highlight parsing them and enable Mermaid to parse them. + */ +function fixMermaid() { + let mermaids = []; + [].push.apply(mermaids, document.getElementsByClassName('language-mermaid')); + for (let i = 0; i < mermaids.length; i++) { + $(mermaids[i]).unwrap('pre'); // Remove
 wrapper.
+    $(mermaids[i]).replaceWith(function () {
+      // Convert  block to 
and add `mermaid` class so that Mermaid will parse it. + return $("
").append($(this).contents()).addClass('mermaid'); + }); + } +} + +/* --------------------------------------------------------------------------- + * On document ready. + * --------------------------------------------------------------------------- */ + +$(document).ready(function () { + fixHugoOutput(); + fixMermaid(); + + // Initialise code highlighting if enabled for this page. + // Note: this block should be processed after the Mermaid code-->div conversion. + if (code_highlighting) { + hljs.initHighlighting(); + } + + // Initialize theme variation. + initThemeVariation(); + + // Change theme mode. + $('.js-set-theme-light').click(function (e) { + e.preventDefault(); + changeThemeModeClick(2); + }); + $('.js-set-theme-dark').click(function (e) { + e.preventDefault(); + changeThemeModeClick(0); + }); + $('.js-set-theme-auto').click(function (e) { + e.preventDefault(); + changeThemeModeClick(1); + }); + + // Live update of day/night mode on system preferences update (no refresh required). + // Note: since we listen only for *dark* events, we won't detect other scheme changes such as light to no-preference. + const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + darkModeMediaQuery.addEventListener("change", (e) => { + if (!canChangeTheme()) { + // Changing theme variation is not allowed by admin. + return; + } + const darkModeOn = e.matches; + console.log(`OS dark mode preference changed to ${darkModeOn ? '🌒 on' : '☀️ off'}.`); + let currentThemeVariation = parseInt(localStorage.getItem('dark_mode') || 2); + let isDarkTheme; + if (currentThemeVariation === 2) { + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + // The visitor prefers dark themes. + isDarkTheme = true; + } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { + // The visitor prefers light themes. + isDarkTheme = false; + } else { + // The visitor does not have a day or night preference, so use the theme's default setting. + isDarkTheme = isSiteThemeDark; } - const darkModeOn = e.matches; - console.log(`OS dark mode preference changed to ${darkModeOn ? '🌒 on' : '☀️ off'}.`); - let currentThemeVariation = parseInt(localStorage.getItem('dark_mode') || 2); - let isDarkTheme; - if (currentThemeVariation === 2) { - if (window.matchMedia('(prefers-color-scheme: dark)').matches) { - // The visitor prefers dark themes. - isDarkTheme = true; - } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { - // The visitor prefers light themes. - isDarkTheme = false; - } else { - // The visitor does not have a day or night preference, so use the theme's default setting. - isDarkTheme = isSiteThemeDark; - } - renderThemeVariation(isDarkTheme); + renderThemeVariation(isDarkTheme); + } + }); +}); + +/* --------------------------------------------------------------------------- + * On window loaded. + * --------------------------------------------------------------------------- */ + +$(window).on('load', function () { + // On page load, scroll to hash (if set) in URL + // If URL contains a hash and there are no dynamically loaded images on the page, + // immediately scroll to target ID taking into account responsive offset. + // Otherwise, wait for `imagesLoaded()` to complete before scrolling to hash to prevent scrolling to wrong + // location. + if (window.location.hash && !$('.projects-container').length) { + scrollToAnchor(); + } + + // Filter projects. + $('.projects-container').each(function (index, container) { + let $container = $(container); + let $section = $container.closest('section'); + let layout; + if ($section.find('.isotope').hasClass('js-layout-row')) { + layout = 'fitRows'; + } else { + layout = 'masonry'; + } + + $container.imagesLoaded(function () { + // Initialize Isotope after all images have loaded. + $container.isotope({ + itemSelector: '.isotope-item', + layoutMode: layout, + masonry: { + gutter: 20 + }, + filter: $section.find('.default-project-filter').text() + }); + + // Filter items when filter link is clicked. + $section.find('.project-filters a').click(function () { + let selector = $(this).attr('data-filter'); + $container.isotope({filter: selector}); + $(this).removeClass('active').addClass('active').siblings().removeClass('active all'); + return false; + }); + + // If window hash is set, scroll to hash. + // Placing this within `imagesLoaded` prevents scrolling to the wrong location due to dynamic image loading + // affecting page layout and position of the target anchor ID. + // Note: If there are multiple project widgets on a page, ideally only perform this once after images + // from *all* project widgets have finished loading. + if (window.location.hash) { + scrollToAnchor(); } }); }); - /* --------------------------------------------------------------------------- - * On window loaded. - * --------------------------------------------------------------------------- */ + // Enable publication filter for publication index page. + if ($('.pub-filters-select')) { + filter_publications(); + // Useful for changing hash manually (e.g. in development): + // window.addEventListener('hashchange', filter_publications, false); + } - $(window).on('load', function () { - // On page load, scroll to hash (if set) in URL - // If URL contains a hash and there are no dynamically loaded images on the page, - // immediately scroll to target ID taking into account responsive offset. - // Otherwise, wait for `imagesLoaded()` to complete before scrolling to hash to prevent scrolling to wrong - // location. - if (window.location.hash && !$('.projects-container').length) { - scrollToAnchor(); - } - - // Filter projects. - $('.projects-container').each(function (index, container) { - let $container = $(container); - let $section = $container.closest('section'); - let layout; - if ($section.find('.isotope').hasClass('js-layout-row')) { - layout = 'fitRows'; + // Load citation modal on 'Cite' click. + $('.js-cite-modal').click(function (e) { + e.preventDefault(); + let filename = $(this).attr('data-filename'); + let modal = $('#modal'); + modal.find('.modal-body code').load(filename, function (response, status, xhr) { + if (status == 'error') { + let msg = "Error: "; + $('#modal-error').html(msg + xhr.status + " " + xhr.statusText); } else { - layout = 'masonry'; + $('.js-download-cite').attr('href', filename); } - - $container.imagesLoaded(function () { - // Initialize Isotope after all images have loaded. - $container.isotope({ - itemSelector: '.isotope-item', - layoutMode: layout, - masonry: { - gutter: 20 - }, - filter: $section.find('.default-project-filter').text() - }); - - // Filter items when filter link is clicked. - $section.find('.project-filters a').click(function () { - let selector = $(this).attr('data-filter'); - $container.isotope({filter: selector}); - $(this).removeClass('active').addClass('active').siblings().removeClass('active all'); - return false; - }); - - // If window hash is set, scroll to hash. - // Placing this within `imagesLoaded` prevents scrolling to the wrong location due to dynamic image loading - // affecting page layout and position of the target anchor ID. - // Note: If there are multiple project widgets on a page, ideally only perform this once after images - // from *all* project widgets have finished loading. - if (window.location.hash) { - scrollToAnchor(); - } - }); }); + modal.modal('show'); + }); - // Enable publication filter for publication index page. - if ($('.pub-filters-select')) { - filter_publications(); - // Useful for changing hash manually (e.g. in development): - // window.addEventListener('hashchange', filter_publications, false); + // Copy citation text on 'Copy' click. + $('.js-copy-cite').click(function (e) { + e.preventDefault(); + // Get selection. + let range = document.createRange(); + let code_node = document.querySelector('#modal .modal-body'); + range.selectNode(code_node); + window.getSelection().addRange(range); + try { + // Execute the copy command. + document.execCommand('copy'); + } catch (e) { + console.log('Error: citation copy failed.'); } + // Remove selection. + window.getSelection().removeRange(range); + }); - // Load citation modal on 'Cite' click. - $('.js-cite-modal').click(function (e) { - e.preventDefault(); - let filename = $(this).attr('data-filename'); - let modal = $('#modal'); - modal.find('.modal-body code').load(filename, function (response, status, xhr) { - if (status == 'error') { - let msg = "Error: "; - $('#modal-error').html(msg + xhr.status + " " + xhr.statusText); - } else { - $('.js-download-cite').attr('href', filename); - } - }); - modal.modal('show'); - }); + // Initialise Google Maps if necessary. + initMap(); - // Copy citation text on 'Copy' click. - $('.js-copy-cite').click(function (e) { - e.preventDefault(); - // Get selection. - let range = document.createRange(); - let code_node = document.querySelector('#modal .modal-body'); - range.selectNode(code_node); - window.getSelection().addRange(range); - try { - // Execute the copy command. - document.execCommand('copy'); - } catch (e) { - console.log('Error: citation copy failed.'); - } - // Remove selection. - window.getSelection().removeRange(range); - }); + // Print latest version of GitHub projects. + let githubReleaseSelector = '.js-github-release'; + if ($(githubReleaseSelector).length > 0) + printLatestRelease(githubReleaseSelector, $(githubReleaseSelector).data('repo')); - // Initialise Google Maps if necessary. - initMap(); - - // Print latest version of GitHub projects. - let githubReleaseSelector = '.js-github-release'; - if ($(githubReleaseSelector).length > 0) - printLatestRelease(githubReleaseSelector, $(githubReleaseSelector).data('repo')); - - // On search icon click toggle search dialog. - $('.js-search').click(function (e) { - e.preventDefault(); - toggleSearchDialog(); - }); - $(document).on('keydown', function (e) { - if (e.which == 27) { - // `Esc` key pressed. - if ($('body').hasClass('searching')) { - toggleSearchDialog(); - } - } else if (e.which == 191 && e.shiftKey == false && !$('input,textarea').is(':focus')) { - // `/` key pressed outside of text input. - e.preventDefault(); + // On search icon click toggle search dialog. + $('.js-search').click(function (e) { + e.preventDefault(); + toggleSearchDialog(); + }); + $(document).on('keydown', function (e) { + if (e.which == 27) { + // `Esc` key pressed. + if ($('body').hasClass('searching')) { toggleSearchDialog(); } - }); - - // Init. author notes (tooltips). - $('[data-toggle="tooltip"]').tooltip(); - - // Re-initialize Scrollspy with dynamic navbar height offset. - fixScrollspy(); + } else if (e.which == 191 && e.shiftKey == false && !$('input,textarea').is(':focus')) { + // `/` key pressed outside of text input. + e.preventDefault(); + toggleSearchDialog(); + } }); - // Normalize Bootstrap carousel slide heights. - $(window).on('load resize orientationchange', normalizeCarouselSlideHeights); + // Init. author notes (tooltips). + $('[data-toggle="tooltip"]').tooltip(); - // Automatic main menu dropdowns on mouse over. - $('body').on('mouseenter mouseleave', '.dropdown', function (e) { - var dropdown = $(e.target).closest('.dropdown'); - var menu = $('.dropdown-menu', dropdown); - dropdown.addClass('show'); - menu.addClass('show'); - setTimeout(function () { - dropdown[dropdown.is(':hover') ? 'addClass' : 'removeClass']('show'); - menu[dropdown.is(':hover') ? 'addClass' : 'removeClass']('show'); - }, 300); - }); + // Re-initialize Scrollspy with dynamic navbar height offset. + fixScrollspy(); +}); - // Call `fixScrollspy` when window is resized. - let resizeTimer; - $(window).resize(function () { - clearTimeout(resizeTimer); - resizeTimer = setTimeout(fixScrollspy, 200); - }); +// Normalize Bootstrap carousel slide heights. +$(window).on('load resize orientationchange', normalizeCarouselSlideHeights); -})(jQuery); +// Automatic main menu dropdowns on mouse over. +$('body').on('mouseenter mouseleave', '.dropdown', function (e) { + var dropdown = $(e.target).closest('.dropdown'); + var menu = $('.dropdown-menu', dropdown); + dropdown.addClass('show'); + menu.addClass('show'); + setTimeout(function () { + dropdown[dropdown.is(':hover') ? 'addClass' : 'removeClass']('show'); + menu[dropdown.is(':hover') ? 'addClass' : 'removeClass']('show'); + }, 300); +}); + +// Call `fixScrollspy` when window is resized. +let resizeTimer; +$(window).resize(function () { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(fixScrollspy, 200); +}); diff --git a/wowchemy/config.yaml b/wowchemy/config.yaml index ab2ecbef..53067f71 100644 --- a/wowchemy/config.yaml +++ b/wowchemy/config.yaml @@ -8,7 +8,7 @@ outputFormats: rel: manifest module: hugoVersion: - min: '0.73.0' + min: '0.78.0' extended: true mounts: - source: content diff --git a/wowchemy/layouts/_default/baseof.html b/wowchemy/layouts/_default/baseof.html index 944320d9..6967a11d 100644 --- a/wowchemy/layouts/_default/baseof.html +++ b/wowchemy/layouts/_default/baseof.html @@ -8,18 +8,12 @@ {{- $highlight_active_link := site.Params.main_menu.highlight_active_link | default true -}} - {{/* Load day/night theme. */}} - {{/* Initialise default theme. */}} - {{ if site.Params.day_night }} - - {{ end }} - {{ if eq (.Scratch.Get "light") true }} - - {{ else }} - - {{ end }} - {{ $load_theme := resources.Get "js/load-theme.js" }} - + {{/* Initialise theme variation. */}} + {{ $wcDarkLightEnabled := site.Params.day_night | default false }} + {{ $wcIsSiteThemeDark := not (.Scratch.Get "light") | default false }} + {{ $js_params := dict "wcDarkLightEnabled" $wcDarkLightEnabled "wcIsSiteThemeDark" $wcIsSiteThemeDark }} + {{ $js_file := resources.Get "js/wowchemy-init.js" | js.Build (dict "params" $js_params) }} + {{ partial "search" . }} diff --git a/wowchemy/layouts/partials/site_js.html b/wowchemy/layouts/partials/site_js.html index 74de8252..5c698e52 100644 --- a/wowchemy/layouts/partials/site_js.html +++ b/wowchemy/layouts/partials/site_js.html @@ -155,7 +155,7 @@ {{ $js_comment := printf "/* Wowchemy v%s | https://wowchemy.com/ */\n" site.Data.wowchemy.version }} {{ $js_bundle_head := $js_comment | resources.FromString "js/bundle-head.js" }} {{ $js_linebreak := "\n" | resources.FromString "js/linebreak.js" }}{{/* Fix no line break after Bootstrap JS causing error. */}} - {{ $js_academic := resources.Get "js/wowchemy.js" }} + {{ $js_academic := resources.Get "js/wowchemy.js" | js.Build }} {{ $js_academic_search := resources.Get "js/wowchemy-search.js" }} {{ $js_algolia_search := resources.Get "js/algolia-search.js" }} {{ $js_bootstrap := resources.Get "js/_vendor/bootstrap.bundle.js" }}