perf: replace jQuery with Vanilla JS (#1681)

Also replaced `magnific-popup` with `GLightbox`
This commit is contained in:
Cotes Chung 2024-04-17 06:10:01 +08:00 committed by GitHub
parent c85e9e2394
commit fe7afa379f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 274 additions and 305 deletions

View file

@ -4,9 +4,6 @@ webfonts: /assets/lib/fonts/main.css
# Libraries # Libraries
jquery:
js: /assets/lib/jquery/jquery.min.js
bootstrap: bootstrap:
css: /assets/lib/bootstrap/bootstrap.min.css css: /assets/lib/bootstrap/bootstrap.min.css
js: /assets/lib/bootstrap/bootstrap.bundle.min.js js: /assets/lib/bootstrap/bootstrap.bundle.min.js
@ -31,9 +28,9 @@ dayjs:
relativeTime: /assets/lib/dayjs/plugin/relativeTime.min.js relativeTime: /assets/lib/dayjs/plugin/relativeTime.min.js
localizedFormat: /assets/lib/dayjs/plugin/localizedFormat.min.js localizedFormat: /assets/lib/dayjs/plugin/localizedFormat.min.js
magnific-popup: glightbox:
css: /assets/lib/magnific-popup/magnific-popup.css css: /assets/lib/glightbox/glightbox.min.css
js: /assets/lib/magnific-popup/jquery.magnific-popup.min.js js: /assets/lib/glightbox/glightbox.min.js
lazy-polyfill: lazy-polyfill:
css: /assets/lib/loading-attribute-polyfill/loading-attribute-polyfill.min.css css: /assets/lib/loading-attribute-polyfill/loading-attribute-polyfill.min.css

View file

@ -17,9 +17,6 @@ webfonts: https://fonts.googleapis.com/css2?family=Lato:wght@300;400&family=Sour
# Libraries # Libraries
jquery:
js: https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
bootstrap: bootstrap:
css: https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css css: https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css
js: https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js js: https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js
@ -44,9 +41,9 @@ dayjs:
relativeTime: https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/relativeTime.min.js relativeTime: https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/relativeTime.min.js
localizedFormat: https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/localizedFormat.min.js localizedFormat: https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/localizedFormat.min.js
magnific-popup: glightbox:
css: https://cdn.jsdelivr.net/npm/magnific-popup@1.1.0/dist/magnific-popup.min.css css: https://cdn.jsdelivr.net/npm/glightbox@3.3.0/dist/css/glightbox.min.css
js: https://cdn.jsdelivr.net/npm/magnific-popup@1.1.0/dist/jquery.magnific-popup.min.js js: https://cdn.jsdelivr.net/npm/glightbox@3.3.0/dist/js/glightbox.min.js
lazy-polyfill: lazy-polyfill:
css: https://cdn.jsdelivr.net/npm/loading-attribute-polyfill@2.1.1/dist/loading-attribute-polyfill.min.css css: https://cdn.jsdelivr.net/npm/loading-attribute-polyfill@2.1.1/dist/loading-attribute-polyfill.min.css

View file

@ -28,7 +28,7 @@
{ threshold: [0] } { threshold: [0] }
); );
disqus_observer.observe(document.querySelector('#disqus_thread')); disqus_observer.observe(document.getElementById('disqus_thread'));
/* Auto switch theme */ /* Auto switch theme */
function reloadDisqus() { function reloadDisqus() {
@ -44,7 +44,7 @@
} }
} }
if (document.querySelector('.mode-toggle')) { if (document.getElementById('mode-toggle')) {
window.addEventListener('message', reloadDisqus); window.addEventListener('message', reloadDisqus);
} }
</script> </script>

View file

@ -2,7 +2,6 @@
<script type="text/javascript"> <script type="text/javascript">
(function () { (function () {
const origin = 'https://giscus.app'; const origin = 'https://giscus.app';
const iframe = 'iframe.giscus-frame';
const lightTheme = 'light'; const lightTheme = 'light';
const darkTheme = 'dark_dimmed'; const darkTheme = 'dark_dimmed';
@ -58,7 +57,7 @@
} }
}; };
const giscus = document.querySelector(iframe).contentWindow; const giscus = document.getElementsByClassName('giscus-frame')[0].contentWindow;
giscus.postMessage({ giscus: message }, origin); giscus.postMessage({ giscus: message }, origin);
} }
}); });

View file

@ -10,7 +10,6 @@
<script type="text/javascript"> <script type="text/javascript">
(function () { (function () {
const origin = 'https://utteranc.es'; const origin = 'https://utteranc.es';
const iframe = 'iframe.utterances-frame';
const lightTheme = 'github-light'; const lightTheme = 'github-light';
const darkTheme = 'github-dark'; const darkTheme = 'github-dark';
let initTheme = lightTheme; let initTheme = lightTheme;
@ -43,7 +42,7 @@
theme: theme theme: theme
}; };
const utterances = document.querySelector(iframe).contentWindow; const utterances = document.getElementsByClassName('utterances-frame')[0].contentWindow;
utterances.postMessage(message, origin); utterances.postMessage(message, origin);
}); });
})(); })();

View file

@ -97,8 +97,8 @@
{% endif %} {% endif %}
{% if page.layout == 'page' or page.layout == 'post' %} {% if page.layout == 'page' or page.layout == 'post' %}
<!-- Manific Popup --> <!-- Image Popup -->
<link rel="stylesheet" href="{{ site.data.origin[type].magnific-popup.css | relative_url }}"> <link rel="stylesheet" href="{{ site.data.origin[type].glightbox.css | relative_url }}">
{% endif %} {% endif %}
<!-- JavaScript --> <!-- JavaScript -->

View file

@ -2,12 +2,9 @@
<!-- commons --> <!-- commons -->
{% assign urls = site.data.origin[type].jquery.js {%- capture urls -%}
| append: ',' {{ site.data.origin[type].bootstrap.js }},{{ site.data.origin[type].search.js }}
| append: site.data.origin[type].bootstrap.js {%- endcapture -%}
| append: ','
| append: site.data.origin[type].search.js
%}
<!-- layout specified --> <!-- layout specified -->
@ -20,7 +17,7 @@
<!-- image lazy-loading & popup & clipboard --> <!-- image lazy-loading & popup & clipboard -->
{% assign urls = urls {% assign urls = urls
| append: ',' | append: ','
| append: site.data.origin[type]['magnific-popup'].js | append: site.data.origin[type].glightbox.js
| append: ',' | append: ','
| append: site.data.origin[type].clipboard.js | append: site.data.origin[type].clipboard.js
%} %}

View file

@ -1,6 +1,5 @@
<!-- mermaid-js loader --> <!-- mermaid-js loader -->
<script type="text/javascript"> <script type="text/javascript">
(function () {
function updateMermaid(event) { function updateMermaid(event) {
if (event.source === window && event.data && event.data.direction === ModeToggle.ID) { if (event.source === window && event.data && event.data.direction === ModeToggle.ID) {
const mode = event.data.message; const mode = event.data.message;
@ -13,10 +12,12 @@
let config = { theme: expectedTheme }; let config = { theme: expectedTheme };
/* Re-render the SVG <https://github.com/mermaid-js/mermaid/issues/311#issuecomment-332557344> */ /* Re-render the SVG <https://github.com/mermaid-js/mermaid/issues/311#issuecomment-332557344> */
$('.mermaid').each(function () { const mermaidList = document.getElementsByClassName('mermaid');
let svgCode = $(this).prev().children().html();
$(this).removeAttr('data-processed'); [...mermaidList].forEach((elem) => {
$(this).html(svgCode); const svgCode = elem.previousSibling.children.item(0).innerHTML;
elem.innerHTML = svgCode;
elem.removeAttribute('data-processed');
}); });
mermaid.initialize(config); mermaid.initialize(config);
@ -24,6 +25,7 @@
} }
} }
(function () {
let initTheme = 'default'; let initTheme = 'default';
const html = document.documentElement; const html = document.documentElement;
@ -35,11 +37,12 @@
} }
let mermaidConf = { let mermaidConf = {
theme: initTheme /* <default|dark|forest|neutral> */ theme: initTheme /* <default | dark | forest | neutral> */
}; };
/* Create mermaid tag */ /* Create mermaid tag */
document.querySelectorAll('pre>code.language-mermaid').forEach((elem) => { const basicList = document.getElementsByClassName('language-mermaid');
[...basicList].forEach((elem) => {
const svgCode = elem.textContent; const svgCode = elem.textContent;
const backup = elem.parentElement; const backup = elem.parentElement;
backup.classList.add('unloaded'); backup.classList.add('unloaded');
@ -52,7 +55,6 @@
}); });
mermaid.initialize(mermaidConf); mermaid.initialize(mermaidConf);
window.addEventListener('message', updateMermaid); window.addEventListener('message', updateMermaid);
})(); })();
</script> </script>

View file

@ -44,7 +44,7 @@
<div class="sidebar-bottom d-flex flex-wrap align-items-center w-100"> <div class="sidebar-bottom d-flex flex-wrap align-items-center w-100">
{% unless site.theme_mode %} {% unless site.theme_mode %}
<button type="button" class="mode-toggle btn" aria-label="Switch Mode"> <button type="button" class="btn" aria-label="Switch Mode" id="mode-toggle">
<i class="fas fa-adjust"></i> <i class="fas fa-adjust"></i>
</button> </button>

View file

@ -59,7 +59,7 @@
<i class="fas fa-search fa-fw"></i> <i class="fas fa-search fa-fw"></i>
</button> </button>
<search class="align-items-center ms-3 ms-lg-0"> <search id="search" class="align-items-center ms-3 ms-lg-0">
<i class="fas fa-search fa-fw"></i> <i class="fas fa-search fa-fw"></i>
<input <input
class="form-control" class="form-control"

View file

@ -3,18 +3,17 @@
*/ */
export function back2top() { export function back2top() {
const $window = $(window); const btn = document.getElementById('back-to-top');
const $btn = $('#back-to-top');
$window.on('scroll', () => { window.addEventListener('scroll', () => {
if ($window.scrollTop() > 50) { if (window.scrollY > 50) {
$btn.fadeIn(); btn.classList.add('show');
} else { } else {
$btn.fadeOut(); btn.classList.remove('show');
} }
}); });
$btn.on('click', () => { btn.addEventListener('click', () => {
$window.scrollTop(0); window.scrollTo({ top: 0 });
}); });
} }

View file

@ -3,34 +3,31 @@
*/ */
const childPrefix = 'l_'; const childPrefix = 'l_';
const parentPrefix = 'h_'; const parentPrefix = 'h_';
const collapse = $('.collapse'); const children = document.getElementsByClassName('collapse');
export function categoryCollapse() { export function categoryCollapse() {
/* close up top-category */ [...children].forEach((elem) => {
collapse.on('hide.bs.collapse', function () { const id = parentPrefix + elem.id.substring(childPrefix.length);
/* Bootstrap collapse events. */ const parentId = const parent = document.getElementById(id);
parentPrefix + $(this).attr('id').substring(childPrefix.length);
if (parentId) { // collapse sub-categories
$(`#${parentId} .far.fa-folder-open`).attr( elem.addEventListener('hide.bs.collapse', () => {
'class', if (parent) {
'far fa-folder fa-fw' parent.querySelector('.far.fa-folder-open').className =
); 'far fa-folder fa-fw';
$(`#${parentId} i.fas`).addClass('rotate'); parent.querySelector('.fas.fa-angle-down').classList.add('rotate');
$(`#${parentId}`).removeClass('hide-border-bottom'); parent.classList.remove('hide-border-bottom');
} }
}); });
/* expand the top category */ // expand sub-categories
collapse.on('show.bs.collapse', function () { elem.addEventListener('show.bs.collapse', () => {
const parentId = if (parent) {
parentPrefix + $(this).attr('id').substring(childPrefix.length); parent.querySelector('.far.fa-folder').className =
if (parentId) { 'far fa-folder-open fa-fw';
$(`#${parentId} .far.fa-folder`).attr( parent.querySelector('.fas.fa-angle-down').classList.remove('rotate');
'class', parent.classList.add('hide-border-bottom');
'far fa-folder-open fa-fw'
);
$(`#${parentId} i.fas`).removeClass('rotate');
$(`#${parentId}`).addClass('hide-border-bottom');
} }
}); });
});
} }

View file

@ -7,69 +7,70 @@
*/ */
const clipboardSelector = '.code-header>button'; const clipboardSelector = '.code-header>button';
const ICON_DEFAULT = 'far fa-clipboard';
const ICON_SUCCESS = 'fas fa-check'; const ICON_SUCCESS = 'fas fa-check';
const ATTR_TIMEOUT = 'timeout'; const ATTR_TIMEOUT = 'timeout';
const ATTR_TITLE_SUCCEED = 'data-title-succeed'; const ATTR_TITLE_SUCCEED = 'data-title-succeed';
const ATTR_TITLE_ORIGIN = 'data-bs-original-title'; const ATTR_TITLE_ORIGIN = 'data-bs-original-title';
const TIMEOUT = 2000; // in milliseconds const TIMEOUT = 2000; // in milliseconds
function isLocked(node) { function isLocked(node) {
if ($(node)[0].hasAttribute(ATTR_TIMEOUT)) { if (node.hasAttribute(ATTR_TIMEOUT)) {
let timeout = $(node).attr(ATTR_TIMEOUT); let timeout = node.getAttribute(ATTR_TIMEOUT);
if (Number(timeout) > Date.now()) { if (Number(timeout) > Date.now()) {
return true; return true;
} }
} }
return false; return false;
} }
function lock(node) { function lock(node) {
$(node).attr(ATTR_TIMEOUT, Date.now() + TIMEOUT); node.setAttribute(ATTR_TIMEOUT, Date.now() + TIMEOUT);
} }
function unlock(node) { function unlock(node) {
$(node).removeAttr(ATTR_TIMEOUT); node.removeAttribute(ATTR_TIMEOUT);
} }
function getIcon(btn) {
let iconNode = $(btn).children();
return iconNode.attr('class');
}
const ICON_DEFAULT = getIcon(clipboardSelector);
function showTooltip(btn) { function showTooltip(btn) {
const succeedTitle = $(btn).attr(ATTR_TITLE_SUCCEED); const succeedTitle = btn.getAttribute(ATTR_TITLE_SUCCEED);
$(btn).attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show'); btn.setAttribute(ATTR_TITLE_ORIGIN, succeedTitle);
bootstrap.Tooltip.getInstance(btn).show();
} }
function hideTooltip(btn) { function hideTooltip(btn) {
$(btn).tooltip('hide').removeAttr(ATTR_TITLE_ORIGIN); bootstrap.Tooltip.getInstance(btn).hide();
btn.removeAttribute(ATTR_TITLE_ORIGIN);
} }
function setSuccessIcon(btn) { function setSuccessIcon(btn) {
let btnNode = $(btn); const icon = btn.children[0];
let iconNode = btnNode.children(); icon.setAttribute('class', ICON_SUCCESS);
iconNode.attr('class', ICON_SUCCESS);
} }
function resumeIcon(btn) { function resumeIcon(btn) {
let btnNode = $(btn); const icon = btn.children[0];
let iconNode = btnNode.children(); icon.setAttribute('class', ICON_DEFAULT);
iconNode.attr('class', ICON_DEFAULT);
} }
export function initClipboard() { export function initClipboard() {
const clipboardList = document.querySelectorAll(clipboardSelector);
if (clipboardList.length === 0) {
return;
}
// Initial the clipboard.js object // Initial the clipboard.js object
if ($(clipboardSelector).length) {
const clipboard = new ClipboardJS(clipboardSelector, { const clipboard = new ClipboardJS(clipboardSelector, {
target(trigger) { target: (trigger) => {
let codeBlock = trigger.parentNode.nextElementSibling; const codeBlock = trigger.parentNode.nextElementSibling;
return codeBlock.querySelector('code .rouge-code'); return codeBlock.querySelector('code .rouge-code');
} }
}); });
const clipboardList = document.querySelectorAll(clipboardSelector);
[...clipboardList].map( [...clipboardList].map(
(elem) => (elem) =>
new bootstrap.Tooltip(elem, { new bootstrap.Tooltip(elem, {
@ -78,9 +79,10 @@ export function initClipboard() {
); );
clipboard.on('success', (e) => { clipboard.on('success', (e) => {
const trigger = e.trigger;
e.clearSelection(); e.clearSelection();
const trigger = e.trigger;
if (isLocked(trigger)) { if (isLocked(trigger)) {
return; return;
} }
@ -95,14 +97,13 @@ export function initClipboard() {
unlock(trigger); unlock(trigger);
}, TIMEOUT); }, TIMEOUT);
}); });
}
/* --- Post link sharing --- */ /* --- Post link sharing --- */
const btnCopyLink = $('#copy-link'); const btnCopyLink = document.getElementById('copy-link');
btnCopyLink.on('click', (e) => { btnCopyLink.addEventListener('click', (e) => {
let target = $(e.target); const target = e.target;
if (isLocked(target)) { if (isLocked(target)) {
return; return;
@ -110,21 +111,23 @@ export function initClipboard() {
// Copy URL to clipboard // Copy URL to clipboard
navigator.clipboard.writeText(window.location.href).then(() => { navigator.clipboard.writeText(window.location.href).then(() => {
const defaultTitle = target.attr(ATTR_TITLE_ORIGIN); const defaultTitle = target.getAttribute(ATTR_TITLE_ORIGIN);
const succeedTitle = target.attr(ATTR_TITLE_SUCCEED); const succeedTitle = target.getAttribute(ATTR_TITLE_SUCCEED);
// Switch tooltip title // Switch tooltip title
target.attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show'); target.setAttribute(ATTR_TITLE_ORIGIN, succeedTitle);
bootstrap.Tooltip.getInstance(target).show();
lock(target); lock(target);
setTimeout(() => { setTimeout(() => {
target.attr(ATTR_TITLE_ORIGIN, defaultTitle); target.setAttribute(ATTR_TITLE_ORIGIN, defaultTitle);
unlock(target); unlock(target);
}, TIMEOUT); }, TIMEOUT);
}); });
}); });
btnCopyLink.on('mouseleave', function (e) { btnCopyLink.addEventListener('mouseleave', (e) => {
const target = $(e.target); bootstrap.Tooltip.getInstance(e.target).hide();
target.tooltip('hide');
}); });
} }

View file

@ -11,7 +11,7 @@ const cover = {
}; };
function removeCover(clzss) { function removeCover(clzss) {
$(this).parent().removeClass(clzss); this.parentElement.classList.remove(clzss);
} }
function handleImage() { function handleImage() {
@ -30,32 +30,38 @@ function handleImage() {
* Switches the LQIP with the real image URL. * Switches the LQIP with the real image URL.
*/ */
function switchLQIP() { function switchLQIP() {
const $img = $(this); const src = this.getAttribute(ATTR_DATA_SRC);
const src = $img.attr(ATTR_DATA_SRC); this.setAttribute('src', encodeURI(src));
this.removeAttribute(ATTR_DATA_SRC);
$img.attr('src', encodeURI(src));
$img.removeAttr(ATTR_DATA_SRC);
} }
export function loadImg() { export function loadImg() {
const $images = $('article img'); const images = document.querySelectorAll('article img');
if ($images.length) { if (images.length === 0) {
$images.on('load', handleImage); return;
} }
images.forEach((img) => {
img.addEventListener('load', handleImage);
});
// Images loaded from the browser cache do not trigger the 'load' event // Images loaded from the browser cache do not trigger the 'load' event
$('article img[loading="lazy"]').each(function () { document.querySelectorAll('article img[loading="lazy"]').forEach((img) => {
if (this.complete) { if (img.complete) {
removeCover.call(this, cover.SHIMMER); removeCover.call(img, cover.SHIMMER);
} }
}); });
// LQIPs set by the data URI or WebP will not trigger the 'load' event, // LQIPs set by the data URI or WebP will not trigger the 'load' event,
// so manually convert the URI to the URL of a high-resolution image. // so manually convert the URI to the URL of a high-resolution image.
const $lqips = $(`article img[${ATTR_DATA_LQIP}="true"]`); const lqips = document.querySelectorAll(
`article img[${ATTR_DATA_LQIP}="true"]`
);
if ($lqips.length) { if (lqips.length) {
$lqips.each(switchLQIP); lqips.forEach((lqip) => {
switchLQIP.call(lqip);
});
} }
} }

View file

@ -1,22 +1,15 @@
/** /**
* Set up image popup * Set up image popup
* *
* See: https://github.com/dimsemenov/Magnific-Popup * Dependencies: https://github.com/biati-digital/glightbox
*/ */
const IMG_CLASS = 'popup';
export function imgPopup() { export function imgPopup() {
if ($('.popup') <= 0) { if (document.getElementsByClassName(IMG_CLASS).length === 0) {
return; return;
} }
$('.popup').magnificPopup({ GLightbox({ selector: `.${IMG_CLASS}` });
type: 'image',
closeOnContentClick: true,
showCloseBtn: false,
zoom: {
enabled: true,
duration: 300,
easing: 'ease-in-out'
}
});
} }

View file

@ -15,15 +15,15 @@ class LocaleHelper {
} }
static get locale() { static get locale() {
return $('html').attr('lang').substring(0, 2); return document.documentElement.getAttribute('lang').substring(0, 2);
} }
static getTimestamp(elem) { static getTimestamp(elem) {
return Number(elem.attr(LocaleHelper.attrTimestamp)); // unix timestamp return Number(elem.getAttribute(this.attrTimestamp)); // unix timestamp
} }
static getDateFormat(elem) { static getDateFormat(elem) {
return elem.attr(LocaleHelper.attrDateFormat); return elem.getAttribute(this.attrDateFormat);
} }
} }
@ -31,21 +31,23 @@ export function initLocaleDatetime() {
dayjs.locale(LocaleHelper.locale); dayjs.locale(LocaleHelper.locale);
dayjs.extend(window.dayjs_plugin_localizedFormat); dayjs.extend(window.dayjs_plugin_localizedFormat);
$(`[${LocaleHelper.attrTimestamp}]`).each(function () { document
const date = dayjs.unix(LocaleHelper.getTimestamp($(this))); .querySelectorAll(`[${LocaleHelper.attrTimestamp}]`)
const text = date.format(LocaleHelper.getDateFormat($(this))); .forEach((elem) => {
$(this).text(text); const date = dayjs.unix(LocaleHelper.getTimestamp(elem));
$(this).removeAttr(LocaleHelper.attrTimestamp); const text = date.format(LocaleHelper.getDateFormat(elem));
$(this).removeAttr(LocaleHelper.attrDateFormat); elem.textContent = text;
elem.removeAttribute(LocaleHelper.attrTimestamp);
elem.removeAttribute(LocaleHelper.attrDateFormat);
// setup tooltips // setup tooltips
const tooltip = $(this).attr('data-bs-toggle'); if (
if (typeof tooltip === 'undefined' || tooltip !== 'tooltip') { elem.hasAttribute('data-bs-toggle') &&
return; elem.getAttribute('data-bs-toggle') === 'tooltip'
) {
// see: https://day.js.org/docs/en/display/format#list-of-localized-formats
const tooltipText = date.format('llll');
elem.setAttribute('data-bs-title', tooltipText);
} }
const tooltipText = date.format('llll'); // see: https://day.js.org/docs/en/display/format#list-of-localized-formats
$(this).attr('data-bs-title', tooltipText);
new bootstrap.Tooltip($(this));
}); });
} }

View file

@ -1,21 +1,14 @@
/** /**
* Add listener for theme mode toggle * Add listener for theme mode toggle
*/ */
const $toggleElem = $('.mode-toggle'); const toggle = document.getElementById('mode-toggle');
export function modeWatcher() { export function modeWatcher() {
if ($toggleElem.length === 0) { if (!toggle) {
return; return;
} }
$toggleElem.off().on('click', (e) => { toggle.addEventListener('click', () => {
const $target = $(e.target); modeToggle.flipMode();
let $btn =
$target.prop('tagName') === 'button'.toUpperCase()
? $target
: $target.parent();
modeToggle.flipMode(); // modeToggle: `_includes/mode-toggle.html`
$btn.trigger('blur'); // remove the clicking outline
}); });
} }

View file

@ -1,121 +1,109 @@
/** /**
* This script make #search-result-wrapper switch to unloaded or shown automatically. * This script make #search-result-wrapper switch to unloaded or shown automatically.
*/ */
const $btnSbTrigger = $('#sidebar-trigger');
const $btnSearchTrigger = $('#search-trigger');
const $btnCancel = $('#search-cancel');
const $content = $('#main-wrapper>.container>.row');
const $topbarTitle = $('#topbar-title');
const $search = $('search');
const $resultWrapper = $('#search-result-wrapper');
const $results = $('#search-results');
const $input = $('#search-input');
const $hints = $('#search-hints');
const $viewport = $('html,body');
// class names const btnSbTrigger = document.getElementById('sidebar-trigger');
const C_LOADED = 'loaded'; const btnSearchTrigger = document.getElementById('search-trigger');
const C_UNLOADED = 'unloaded'; const btnCancel = document.getElementById('search-cancel');
const C_FOCUS = 'input-focus'; const content = document.querySelectorAll('#main-wrapper>.container>.row');
const C_FLEX = 'd-flex'; const topbarTitle = document.getElementById('topbar-title');
const search = document.getElementById('search');
const resultWrapper = document.getElementById('search-result-wrapper');
const results = document.getElementById('search-results');
const input = document.getElementById('search-input');
const hints = document.getElementById('search-hints');
class ScrollBlocker { // CSS class names
static offset = 0; const LOADED = 'loaded';
static resultVisible = false; const UNLOADED = 'unloaded';
const FOCUS = 'input-focus';
const FLEX = 'd-flex';
static on() { /* Actions in mobile screens (Sidebar hidden) */
ScrollBlocker.offset = window.scrollY;
$viewport.scrollTop(0);
}
static off() {
$viewport.scrollTop(ScrollBlocker.offset);
}
}
/*--- Actions in mobile screens (Sidebar hidden) ---*/
class MobileSearchBar { class MobileSearchBar {
static on() { static on() {
$btnSbTrigger.addClass(C_UNLOADED); btnSbTrigger.classList.add(UNLOADED);
$topbarTitle.addClass(C_UNLOADED); topbarTitle.classList.add(UNLOADED);
$btnSearchTrigger.addClass(C_UNLOADED); btnSearchTrigger.classList.add(UNLOADED);
$search.addClass(C_FLEX); search.classList.add(FLEX);
$btnCancel.addClass(C_LOADED); btnCancel.classList.add(LOADED);
} }
static off() { static off() {
$btnCancel.removeClass(C_LOADED); btnCancel.classList.remove(LOADED);
$search.removeClass(C_FLEX); search.classList.remove(FLEX);
$btnSbTrigger.removeClass(C_UNLOADED); btnSbTrigger.classList.remove(UNLOADED);
$topbarTitle.removeClass(C_UNLOADED); topbarTitle.classList.remove(UNLOADED);
$btnSearchTrigger.removeClass(C_UNLOADED); btnSearchTrigger.classList.remove(UNLOADED);
} }
} }
class ResultSwitch { class ResultSwitch {
static resultVisible = false;
static on() { static on() {
if (!ScrollBlocker.resultVisible) { if (!this.resultVisible) {
// the block method must be called before $(#main-wrapper>.container) unloaded. resultWrapper.classList.remove(UNLOADED);
ScrollBlocker.on(); content.forEach((el) => {
$resultWrapper.removeClass(C_UNLOADED); el.classList.add(UNLOADED);
$content.addClass(C_UNLOADED); });
ScrollBlocker.resultVisible = true; this.resultVisible = true;
} }
} }
static off() { static off() {
if (ScrollBlocker.resultVisible) { if (this.resultVisible) {
$results.empty(); results.innerHTML = '';
if ($hints.hasClass(C_UNLOADED)) {
$hints.removeClass(C_UNLOADED); if (hints.classList.contains(UNLOADED)) {
hints.classList.remove(UNLOADED);
} }
$resultWrapper.addClass(C_UNLOADED);
$content.removeClass(C_UNLOADED);
// now the release method must be called after $(#main-wrapper>.container) display resultWrapper.classList.add(UNLOADED);
ScrollBlocker.off(); content.forEach((el) => {
el.classList.remove(UNLOADED);
$input.val(''); });
ScrollBlocker.resultVisible = false; input.textContent = '';
this.resultVisible = false;
} }
} }
} }
function isMobileView() { function isMobileView() {
return $btnCancel.hasClass(C_LOADED); return btnCancel.classList.contains(LOADED);
} }
export function displaySearch() { export function displaySearch() {
$btnSearchTrigger.on('click', function () { btnSearchTrigger.addEventListener('click', () => {
MobileSearchBar.on(); MobileSearchBar.on();
ResultSwitch.on(); ResultSwitch.on();
$input.trigger('focus'); input.focus();
}); });
$btnCancel.on('click', function () { btnCancel.addEventListener('click', () => {
MobileSearchBar.off(); MobileSearchBar.off();
ResultSwitch.off(); ResultSwitch.off();
}); });
$input.on('focus', function () { input.addEventListener('focus', () => {
$search.addClass(C_FOCUS); search.classList.add(FOCUS);
}); });
$input.on('focusout', function () { input.addEventListener('focusout', () => {
$search.removeClass(C_FOCUS); search.classList.remove(FOCUS);
}); });
$input.on('input', () => { input.addEventListener('input', () => {
if ($input.val() === '') { if (input.value === '') {
if (isMobileView()) { if (isMobileView()) {
$hints.removeClass(C_UNLOADED); hints.classList.remove(UNLOADED);
} else { } else {
ResultSwitch.off(); ResultSwitch.off();
} }
} else { } else {
ResultSwitch.on(); ResultSwitch.on();
if (isMobileView()) { if (isMobileView()) {
$hints.addClass(C_UNLOADED); hints.classList.add(UNLOADED);
} }
} }
}); });

View file

@ -2,7 +2,6 @@
* Expand or close the sidebar in mobile screens. * Expand or close the sidebar in mobile screens.
*/ */
const $body = $('body');
const ATTR_DISPLAY = 'sidebar-display'; const ATTR_DISPLAY = 'sidebar-display';
class SidebarUtil { class SidebarUtil {
@ -10,9 +9,9 @@ class SidebarUtil {
static toggle() { static toggle() {
if (SidebarUtil.isExpanded === false) { if (SidebarUtil.isExpanded === false) {
$body.attr(ATTR_DISPLAY, ''); document.body.setAttribute(ATTR_DISPLAY, '');
} else { } else {
$body.removeAttr(ATTR_DISPLAY); document.body.removeAttribute(ATTR_DISPLAY);
} }
SidebarUtil.isExpanded = !SidebarUtil.isExpanded; SidebarUtil.isExpanded = !SidebarUtil.isExpanded;
@ -20,6 +19,9 @@ class SidebarUtil {
} }
export function sidebarExpand() { export function sidebarExpand() {
$('#sidebar-trigger').on('click', SidebarUtil.toggle); document
$('#mask').on('click', SidebarUtil.toggle); .getElementById('sidebar-trigger')
.addEventListener('click', SidebarUtil.toggle);
document.getElementById('mask').addEventListener('click', SidebarUtil.toggle);
} }

View file

@ -672,18 +672,6 @@ main {
/* --- Overriding --- */ /* --- Overriding --- */
/* magnific-popup */
figure .mfp-title {
text-align: center;
padding-right: 0;
margin-top: 0.5rem;
}
.mfp-img {
transition: none;
}
/* mermaid */ /* mermaid */
.mermaid { .mermaid {
text-align: center; text-align: center;
@ -876,7 +864,7 @@ $btn-mb: 0.5rem;
line-height: $btn-size; line-height: $btn-size;
} }
.mode-toggle { #mode-toggle {
padding: 0; padding: 0;
border: 0; border: 0;
@ -1158,7 +1146,8 @@ search {
/* --- button back-to-top --- */ /* --- button back-to-top --- */
#back-to-top { #back-to-top {
display: none; visibility: hidden;
opacity: 0;
z-index: 1; z-index: 1;
cursor: pointer; cursor: pointer;
position: fixed; position: fixed;
@ -1171,8 +1160,7 @@ search {
height: $back2top-size; height: $back2top-size;
border-radius: 50%; border-radius: 50%;
border: 1px solid var(--btn-backtotop-border-color); border: 1px solid var(--btn-backtotop-border-color);
transition: transform 0.2s ease-out; transition: opacity 0.5s ease-in-out, transform 0.2s ease-out;
-webkit-transition: transform 0.2s ease-out;
&:hover { &:hover {
transform: translate3d(0, -5px, 0); transform: translate3d(0, -5px, 0);
@ -1184,6 +1172,11 @@ search {
position: relative; position: relative;
bottom: 2px; bottom: 2px;
} }
&.show {
opacity: 1;
visibility: visible;
}
} }
#notification { #notification {

View file

@ -8,30 +8,32 @@ if ('serviceWorker' in navigator) {
if (isEnabled) { if (isEnabled) {
const swUrl = '{{ '/sw.min.js' | relative_url }}'; const swUrl = '{{ '/sw.min.js' | relative_url }}';
const $notification = $('#notification'); const notification = document.getElementById('notification');
const $btnRefresh = $('#notification .toast-body>button'); const btnRefresh = notification.querySelector('.toast-body>button');
const popupWindow = bootstrap.Toast.getOrCreateInstance(notification);
navigator.serviceWorker.register(swUrl).then((registration) => { navigator.serviceWorker.register(swUrl).then((registration) => {
{% comment %}In case the user ignores the notification{% endcomment %} {% comment %}In case the user ignores the notification{% endcomment %}
if (registration.waiting) { if (registration.waiting) {
$notification.toast('show'); popupWindow.show();
} }
registration.addEventListener('updatefound', () => { registration.addEventListener('updatefound', () => {
registration.installing.addEventListener('statechange', () => { registration.installing.addEventListener('statechange', () => {
if (registration.waiting) { if (registration.waiting) {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
$notification.toast('show'); popupWindow.show();
} }
} }
}); });
}); });
$btnRefresh.on('click', () => { btnRefresh.addEventListener('click', () => {
if (registration.waiting) { if (registration.waiting) {
registration.waiting.postMessage('SKIP_WAITING'); registration.waiting.postMessage('SKIP_WAITING');
} }
$notification.toast('hide');
popupWindow.hide();
}); });
}); });

@ -1 +1 @@
Subproject commit 7bc0d86b6af83d7acfc63db50f29a5975cec2513 Subproject commit 29d7021f45317d02df9a3297d3c52a30bd4ae795