From 786b0341ee715a6200fe8d67134b8abff1e37282 Mon Sep 17 00:00:00 2001 From: George Cushen Date: Sun, 15 Nov 2020 16:12:52 +0000 Subject: [PATCH] feat: migrate to ES6 modules + Hugo's JS build system The latest Hugo version, v0.78, enables support for JS ES6 module build support within Hugo/Go modules. Initial refactor to ES6 modules with imports/exports. Further refactoring todo. Refactor to use Hugo's new approach to JS param injection. Refactor theming code from JQuery to vanilla JS. Add fix for browser deprecation of the MediaQueryList listener. Requires Hugo 0.78+ See #1929 See #1930 See #1402 --- wowchemy/assets/js/load-theme.js | 47 - wowchemy/assets/js/wowchemy-animation.js | 24 + wowchemy/assets/js/wowchemy-init.js | 18 + wowchemy/assets/js/wowchemy-theming.js | 191 ++++ wowchemy/assets/js/wowchemy.js | 1253 +++++++++------------- wowchemy/config.yaml | 2 +- wowchemy/layouts/_default/baseof.html | 18 +- wowchemy/layouts/partials/site_js.html | 2 +- 8 files changed, 772 insertions(+), 783 deletions(-) delete mode 100644 wowchemy/assets/js/load-theme.js create mode 100644 wowchemy/assets/js/wowchemy-animation.js create mode 100644 wowchemy/assets/js/wowchemy-init.js create mode 100644 wowchemy/assets/js/wowchemy-theming.js 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" }}