refactor theme toggle; add delegate-it

This commit is contained in:
Reorx 2022-05-27 22:40:21 +08:00
parent 28511efd08
commit 47a2fc8153
3 changed files with 163 additions and 48 deletions

90
assets/js/delegate-it.js Normal file
View File

@ -0,0 +1,90 @@
(function() {
/** Keeps track of raw listeners added to the base elements to avoid duplication */
const ledger = new WeakMap();
function editLedger(wanted, baseElement, callback, setup) {
var _a, _b;
if (!wanted && !ledger.has(baseElement)) {
return false;
}
const elementMap = (_a = ledger.get(baseElement)) !== null && _a !== void 0 ? _a : new WeakMap();
ledger.set(baseElement, elementMap);
if (!wanted && !ledger.has(baseElement)) {
return false;
}
const setups = (_b = elementMap.get(callback)) !== null && _b !== void 0 ? _b : new Set();
elementMap.set(callback, setups);
const existed = setups.has(setup);
if (wanted) {
setups.add(setup);
}
else {
setups.delete(setup);
}
return existed && wanted;
}
function isEventTarget(elements) {
return typeof elements.addEventListener === 'function';
}
function safeClosest(event, selector) {
let target = event.target;
if (target instanceof Text) {
target = target.parentElement;
}
if (target instanceof Element && event.currentTarget instanceof Element) {
// `.closest()` may match ancestors of `currentTarget` but we only need its children
const closest = target.closest(selector);
if (closest && event.currentTarget.contains(closest)) {
return closest;
}
}
}
// This type isn't exported as a declaration, so it needs to be duplicated above
function delegate(base, selector, type, callback, options) {
// Handle Selector-based usage
if (typeof base === 'string') {
base = document.querySelectorAll(base);
}
// Handle Array-like based usage
if (!isEventTarget(base)) {
const subscriptions = Array.prototype.map.call(base, (element) => delegate(element, selector, type, callback, options));
return {
destroy() {
for (const subscription of subscriptions) {
subscription.destroy();
}
},
};
}
// `document` should never be the base, it's just an easy way to define "global event listeners"
const baseElement = base instanceof Document ? base.documentElement : base;
// Handle the regular Element usage
const capture = Boolean(typeof options === 'object' ? options.capture : options);
const listenerFn = (event) => {
const delegateTarget = safeClosest(event, selector);
if (delegateTarget) {
event.delegateTarget = delegateTarget;
callback.call(baseElement, event);
}
};
// Drop unsupported `once` option https://github.com/fregante/delegate-it/pull/28#discussion_r863467939
if (typeof options === 'object') {
delete options.once;
}
const setup = JSON.stringify({ selector, type, capture });
const isAlreadyListening = editLedger(true, baseElement, callback, setup);
const delegateSubscription = {
destroy() {
baseElement.removeEventListener(type, listenerFn, options);
editLedger(false, baseElement, callback, setup);
},
};
if (!isAlreadyListening) {
baseElement.addEventListener(type, listenerFn, options);
}
return delegateSubscription;
}
// export default delegate;
window.delegate = delegate;
})();

View File

@ -26,6 +26,27 @@
{{- partial "extend_footer.html" . }} {{- partial "extend_footer.html" . }}
{{/* Load delegate script */}}
{{- $delegate := resources.Get "js/delegate-it.js" }}
<script src="{{ $delegate.RelPermalink }}" data-no-instant
{{- if site.Params.assets.disableFingerprinting }}integrity="{{ $delegate.Data.Integrity }}"{{- end }}
></script>
<script>
(function() {
/* theme toggle */
const disableThemeToggle = '{{ if site.Params.disableThemeToggle }}1{{ end }}' == '1';
if (disableThemeToggle) {
return;
}
// listen to toggle button
document.getElementById("theme-toggle").addEventListener("click", () => {
window.dispatchEvent(new CustomEvent('toggle-theme'));
});
})();
</script>
<script> <script>
(function () { (function () {
let menu = document.getElementById('menu') let menu = document.getElementById('menu')
@ -75,20 +96,6 @@
</script> </script>
{{- end }} {{- end }}
{{- if (not site.Params.disableThemeToggle) }}
<script>
document.getElementById("theme-toggle").addEventListener("click", () => {
if (document.body.className.includes("dark")) {
document.body.classList.remove('dark');
localStorage.setItem("pref-theme", 'light');
} else {
document.body.classList.add('dark');
localStorage.setItem("pref-theme", 'dark');
}
})
</script>
{{- end }}
{{- /* Base64Email */}} {{- /* Base64Email */}}
{{- if (.Param "MaskedEmail") }} {{- if (.Param "MaskedEmail") }}
<script> <script>

View File

@ -1,43 +1,61 @@
{{- /* theme-toggle is enabled */}} <script data-no-instant>
{{- if (not site.Params.disableThemeToggle) }} function switchTheme(theme) {
{{- /* theme is light */}} switch (theme) {
{{- if (eq site.Params.defaultTheme "light") }} case 'light':
<script> document.body.classList.remove('dark');
if (localStorage.getItem("pref-theme") === "dark") { break;
case 'dark':
document.body.classList.add('dark'); document.body.classList.add('dark');
} break;
// auto
</script> default:
{{- /* theme is dark */}}
{{- else if (eq site.Params.defaultTheme "dark") }}
<script>
if (localStorage.getItem("pref-theme") === "light") {
document.body.classList.remove('dark')
}
</script>
{{- else }}
{{- /* theme is auto */}}
<script>
if (localStorage.getItem("pref-theme") === "dark") {
document.body.classList.add('dark');
} else if (localStorage.getItem("pref-theme") === "light") {
document.body.classList.remove('dark')
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.add('dark');
}
</script>
{{- end }}
{{- /* theme-toggle is disabled and theme is auto */}}
{{- else if (and (ne site.Params.defaultTheme "light") (ne site.Params.defaultTheme "dark"))}}
<script>
if (window.matchMedia('(prefers-color-scheme: dark)').matches) { if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.add('dark'); document.body.classList.add('dark');
} }
}
}
function isDarkTheme() {
return document.body.className.includes("dark");
}
function getPrefTheme() {
return localStorage.getItem("pref-theme");
}
function setPrefTheme(theme) {
switchTheme(theme)
localStorage.setItem("pref-theme", theme);
}
const toggleThemeCallbacks = []
toggleThemeCallbacks.push((isDark) => {
// console.log('window toggle-theme')
if (isDark) {
setPrefTheme('light');
} else {
setPrefTheme('dark');
}
})
// listen to set-theme event,
// because window is never changed by InstantClick,
// we add the listener to window to ensure the event is always received
window.addEventListener('toggle-theme', function() {
const isDark = isDarkTheme()
toggleThemeCallbacks.forEach(callback => callback(isDark))
});
</script>
<script>
// load theme, as early as possible
(function() {
const defaultTheme = '{{ site.Params.defaultTheme | default "light" }}';
const prefTheme = getPrefTheme();
const theme = prefTheme ? prefTheme : defaultTheme;
switchTheme(theme);
})();
</script> </script>
{{- end }}
<header class="header"> <header class="header">
<nav class="nav"> <nav class="nav">