From 10ffbca4c0976df654cc2ffd42d106992aaf9a62 Mon Sep 17 00:00:00 2001 From: ZhenShuo Leo <98386542+ZhenShuo2021@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:14:24 +0800 Subject: [PATCH 1/8] refactor(a11y): simply code --- assets/js/a11y.js | 338 +++++++++++++++++++++------------------------- 1 file changed, 156 insertions(+), 182 deletions(-) diff --git a/assets/js/a11y.js b/assets/js/a11y.js index 3117ae72..4c375b7c 100644 --- a/assets/js/a11y.js +++ b/assets/js/a11y.js @@ -1,194 +1,168 @@ -const getA11ySettings = () => { - const settings = localStorage.getItem("a11ySettings"); - return settings - ? JSON.parse(settings) - : { - disableBlur: false, - disableImages: false, - fontSize: "default", - underlineLinks: false, - zenMode: false, - }; -}; +window.A11yPanel = (() => { + const FEATURES = { + disableBlur: { + default: false, + apply: (enabled) => { + document.querySelectorAll("script[data-target-id]").forEach((script) => { + const targetId = script.getAttribute("data-target-id"); + const scrollDivisor = Number(script.getAttribute("data-scroll-divisor") || 300); + if (typeof setBackgroundBlur === "function") { + setBackgroundBlur(targetId, scrollDivisor, enabled, targetId === "menu-blur"); + } + }); + }, + }, -const saveA11ySettings = (settings) => { - localStorage.setItem("a11ySettings", JSON.stringify(settings)); -}; + disableImages: { + default: false, + apply: (enabled) => { + document.querySelectorAll("script[data-target-id]").forEach((script) => { + const image = document.getElementById(script.getAttribute("data-image-id")); + const imageUrl = script.getAttribute("data-image-url"); + if (image) { + image.style.display = enabled ? "none" : ""; + if (!enabled && imageUrl && !image.src) image.src = imageUrl; + } + }); + }, + }, -const applyImageState = (imageElement, imageUrl, disableImages) => { - if (!imageElement) return; - if (disableImages) { - imageElement.style.display = "none"; - } else { - imageElement.style.display = ""; - if (imageUrl && !imageElement.getAttribute("src")) { - imageElement.setAttribute("src", imageUrl); + fontSize: { + default: "default", + apply: (size) => { + document.documentElement.style.fontSize = size === "default" ? "" : size; + }, + }, + + underlineLinks: { + default: false, + apply: (enabled) => { + const existing = document.getElementById("a11y-underline-links"); + if (enabled && !existing) { + const style = document.createElement("style"); + style.id = "a11y-underline-links"; + style.textContent = "a { text-decoration: underline !important; }"; + document.head.appendChild(style); + } else if (!enabled && existing) { + existing.remove(); + } + }, + }, + + zenMode: { + default: false, + apply: (enabled) => { + const isActive = document.body?.classList.contains("zen-mode-enable"); + if (enabled !== isActive) { + const checkbox = document.querySelector('[id$="zen-mode"]'); + if (checkbox && typeof _toggleZenMode === "function") { + _toggleZenMode(checkbox, { scrollToHeader: false }); + } + } + }, + }, + }; + + let settings = null; + + const getSettings = () => { + if (settings) return settings; + const defaults = Object.fromEntries(Object.entries(FEATURES).map(([key, config]) => [key, config.default])); + try { + const saved = localStorage.getItem("a11ySettings"); + settings = { ...defaults, ...JSON.parse(saved || "{}") }; + } catch { + settings = defaults; } - } -}; + return settings; + }; -const applyFontSize = (fontSizePx) => { - const isDefaultSettings = localStorage.getItem("a11ySettings") === null; - if (!isDefaultSettings && fontSizePx !== "default") { - document.documentElement.style.fontSize = fontSizePx; - } -}; - -const applyUnderlineLinks = (enabled) => { - let styleElement = document.getElementById("a11y-underline-links"); - if (enabled) { - if (!styleElement) { - styleElement = document.createElement("style"); - styleElement.id = "a11y-underline-links"; - styleElement.textContent = "a { text-decoration: underline !important; }"; - document.head.appendChild(styleElement); + const updateSetting = (key, value) => { + const current = getSettings(); + current[key] = value; + try { + localStorage.setItem("a11ySettings", JSON.stringify(current)); + } catch (e) { + console.warn(`a11y.js: can not store settings: ${e}`); } - } else { - if (styleElement) { - styleElement.remove(); - } - } -}; + FEATURES[key]?.apply(value); + }; -const applyZenMode = (enabled) => { - const body = document.querySelector("body"); - const isZenModeActive = body && body.classList.contains("zen-mode-enable"); + const initPanel = (panelId) => { + const prefix = panelId.replace("a11y-panel", ""); + const current = getSettings(); - // Toggle only if current state doesn't match desired state - if (enabled !== isZenModeActive) { - const zenModeCheckbox = document.querySelector('[id$="zen-mode"]'); - if (zenModeCheckbox && typeof _toggleZenMode === "function") { - _toggleZenMode(zenModeCheckbox, { scrollToHeader: false }); - } - } -}; + Object.entries(FEATURES).forEach(([key, config]) => { + const elementId = `${prefix}${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; + const element = document.getElementById(elementId) || document.getElementById(`${elementId}-select`); -const applyA11ySettings = () => { - const settings = getA11ySettings(); - document.querySelectorAll("script[data-target-id]").forEach((script) => { - const targetId = script.getAttribute("data-target-id"); - const scrollDivisor = Number(script.getAttribute("data-scroll-divisor") || 300); - const imageId = script.getAttribute("data-image-id"); - const imageUrl = script.getAttribute("data-image-url"); - const isMenuBlur = targetId === "menu-blur"; - setBackgroundBlur(targetId, scrollDivisor, settings.disableBlur, isMenuBlur); - applyImageState(document.getElementById(imageId), imageUrl, settings.disableImages); - }); - applyFontSize(settings.fontSize); - applyUnderlineLinks(settings.underlineLinks); - applyZenMode(settings.zenMode); -}; - -const updateA11ySetting = (key, value) => { - const settings = getA11ySettings(); - settings[key] = value; - saveA11ySettings(settings); - applyA11ySettings(); -}; - -const toggleA11yPanel = (prefix = "") => { - const panel = document.getElementById(`${prefix}a11y-panel`); - const overlay = document.getElementById(`${prefix}a11y-overlay`); - const button = document.getElementById(`${prefix}a11y-toggle`); - if (!panel || !overlay || !button) return; - if (overlay.classList.contains("hidden")) { - overlay.classList.remove("hidden"); - panel.classList.remove("hidden"); - button.setAttribute("aria-pressed", "true"); - button.setAttribute("aria-expanded", "true"); - } else { - overlay.classList.add("hidden"); - panel.classList.add("hidden"); - button.setAttribute("aria-pressed", "false"); - button.setAttribute("aria-expanded", "false"); - } -}; - -const initA11yPanel = (prefix = "") => { - const settings = getA11ySettings(); - const checkboxBlur = document.getElementById(`${prefix}disable-blur`); - const checkboxImages = document.getElementById(`${prefix}disable-images`); - const checkboxUnderline = document.getElementById(`${prefix}underline-links`); - const checkboxZenMode = document.getElementById(`${prefix}zen-mode`); - const fontSizeSelect = document.getElementById(`${prefix}font-size-select`); - const toggleButton = document.getElementById(`${prefix}a11y-toggle`); - const closeButton = document.getElementById(`${prefix}a11y-close`); - const overlay = document.getElementById(`${prefix}a11y-overlay`); - - if ( - !checkboxBlur || - !checkboxImages || - !checkboxUnderline || - !checkboxZenMode || - !fontSizeSelect || - !toggleButton || - !closeButton || - !overlay - ) { - console.warn(`One or more a11y elements not found for prefix: ${prefix}`); - return; - } - - checkboxBlur.checked = settings.disableBlur; - checkboxImages.checked = settings.disableImages; - checkboxUnderline.checked = settings.underlineLinks; - checkboxZenMode.checked = settings.zenMode; - fontSizeSelect.value = settings.fontSize; - - checkboxBlur.addEventListener("change", (e) => updateA11ySetting("disableBlur", e.target.checked)); - checkboxImages.addEventListener("change", (e) => updateA11ySetting("disableImages", e.target.checked)); - checkboxUnderline.addEventListener("change", (e) => updateA11ySetting("underlineLinks", e.target.checked)); - checkboxZenMode.addEventListener("change", (e) => { - // Only save setting, let applyZenMode handle the toggle logic - updateA11ySetting("zenMode", e.target.checked); - }); - fontSizeSelect.addEventListener("change", (e) => { - // Remove fontSize from localStorage when default is selected - if (e.target.value === "default") { - const settings = getA11ySettings(); - delete settings.fontSize; - saveA11ySettings(settings); - document.documentElement.style.fontSize = ""; - } else { - updateA11ySetting("fontSize", e.target.value); - } - }); - - toggleButton.addEventListener("click", () => toggleA11yPanel(prefix)); - closeButton.addEventListener("click", () => toggleA11yPanel(prefix)); - overlay.addEventListener("click", (e) => { - if (e.target === overlay) { - toggleA11yPanel(prefix); - } - }); - - document.querySelectorAll(`.ios-toggle${prefix ? `[id^="${prefix}"]` : ""}`).forEach((toggle) => { - const checkbox = toggle.querySelector('input[type="checkbox"]'); - if (!checkbox) return; - const newToggle = toggle.cloneNode(true); - toggle.parentNode.replaceChild(newToggle, toggle); - newToggle.addEventListener("click", () => { - const newCheckbox = newToggle.querySelector('input[type="checkbox"]'); - if (newCheckbox) { - newCheckbox.checked = !newCheckbox.checked; - newCheckbox.dispatchEvent(new Event("change", { bubbles: true })); + if (element) { + if (element.type === "checkbox") { + element.checked = current[key]; + element.onchange = (e) => updateSetting(key, e.target.checked); + } else if (element.tagName === "SELECT") { + element.value = current[key]; + element.onchange = (e) => updateSetting(key, e.target.value); + } } }); - }); -}; -document.querySelectorAll("script[data-target-id]").forEach((script) => { - const imageId = script.getAttribute("data-image-id"); - const imageUrl = script.getAttribute("data-image-url"); - const settings = getA11ySettings(); - applyImageState(document.getElementById(imageId), imageUrl, settings.disableImages); -}); + const togglePanel = () => { + const panel = document.getElementById(panelId); + const overlay = document.getElementById(`${prefix}a11y-overlay`); + const toggle = document.getElementById(`${prefix}a11y-toggle`); -document.addEventListener("DOMContentLoaded", () => { - applyA11ySettings(); - const allPanels = document.querySelectorAll('[id$="a11y-panel"]'); - allPanels.forEach((panel) => { - const prefix = panel.id.replace("a11y-panel", ""); - initA11yPanel(prefix); - }); -}); + if (!panel || !overlay) return; + + const isHidden = overlay.classList.contains("hidden"); + overlay.classList.toggle("hidden"); + panel.classList.toggle("hidden"); + + if (toggle) { + toggle.setAttribute("aria-pressed", String(isHidden)); + toggle.setAttribute("aria-expanded", String(isHidden)); + } + }; + + const toggle = document.getElementById(`${prefix}a11y-toggle`); + const close = document.getElementById(`${prefix}a11y-close`); + const overlay = document.getElementById(`${prefix}a11y-overlay`); + + if (toggle) toggle.onclick = togglePanel; + if (close) close.onclick = togglePanel; + if (overlay) overlay.onclick = (e) => e.target === overlay && togglePanel(); + }; + + const applyAll = () => { + const current = getSettings(); + Object.entries(current).forEach(([key, value]) => { + FEATURES[key]?.apply(value); + }); + }; + + const init = () => { + applyAll(); + document.querySelectorAll('[id$="a11y-panel"]').forEach((panel) => { + initPanel(panel.id); + }); + }; + + if (getSettings().disableImages) { + FEATURES.disableImages.apply(true); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } + + return { + getSettings, + updateSetting, + addFeature: (name, config) => { + FEATURES[name] = config; + FEATURES[name].apply(getSettings()[name] || config.default); + }, + }; +})(); From ade00547b3d3bf137641080b65216742ac462062 Mon Sep 17 00:00:00 2001 From: ZhenShuo Leo <98386542+ZhenShuo2021@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:39:50 +0800 Subject: [PATCH 2/8] fix(a11y): disableImages not working when layoutBackgroundBlur is false --- assets/js/a11y.js | 12 ++++------ layouts/partials/header/basic.html | 9 ++++--- layouts/partials/hero/thumbAndBackground.html | 24 +++++++------------ layouts/partials/home/background.html | 4 ++-- 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/assets/js/a11y.js b/assets/js/a11y.js index 4c375b7c..48603832 100644 --- a/assets/js/a11y.js +++ b/assets/js/a11y.js @@ -16,14 +16,10 @@ window.A11yPanel = (() => { disableImages: { default: false, apply: (enabled) => { - document.querySelectorAll("script[data-target-id]").forEach((script) => { - const image = document.getElementById(script.getAttribute("data-image-id")); - const imageUrl = script.getAttribute("data-image-url"); - if (image) { - image.style.display = enabled ? "none" : ""; - if (!enabled && imageUrl && !image.src) image.src = imageUrl; - } - }); + const image = document.getElementById("background-image"); + if (image) { + image.style.display = enabled ? "none" : ""; + } }, }, diff --git a/layouts/partials/header/basic.html b/layouts/partials/header/basic.html index 18aab05e..328d04e0 100644 --- a/layouts/partials/header/basic.html +++ b/layouts/partials/header/basic.html @@ -198,14 +198,17 @@
- {{- $toggles := slice - (dict "id" (print $prefix "disable-blur") "label" (i18n "a11y.disable_blur")) + {{ $toggles := slice }} + {{ $shouldDisableBlur := or site.Params.homepage.layoutBackgroundBlur site.Params.article.layoutBackgroundBlur site.Params.list.layoutBackgroundBlur }} + {{ if $shouldDisableBlur }} + {{ $toggles = $toggles | append (dict "id" (print $prefix "disable-blur") "label" (i18n "a11y.disable_blur")) }} + {{ end }} + {{- $toggles = $toggles | append (dict "id" (print $prefix "disable-images") "label" (i18n "a11y.disable_images")) (dict "id" (print $prefix "underline-links") "label" (i18n "a11y.show_link_underline")) (dict "id" (print $prefix "zen-mode") "label" (i18n "article.zen_mode_title.enable")) -}} - {{- range $toggles }}