migrate to decoupled Blowfish layout, fix TOC scrollspy, and update docs
- Theme Decoupling & Cleanup:
- Fully decoupled and vendored the Blowfish theme directly into root directories (layouts/,
assets/, static/, data/, i18n/).
- De-registered and cleaned up legacy Git submodules (themes/blowfish, themes/hugo-admonitions)
from the Git index to protect the build from upstream breakages.
- Configured .gitignore to ignore development preview symlinks (content, static,
config/_default/menus.en.toml), build folders (public/, resources/), and locks.
- TOC & Layout Fixes:
- Replaced the custom layouts/_default/single.html template with the Blowfish default to restore
native container widths and the side-by-side flex layout.
- Restored TOC to float on the right sidebar on desktop viewports and render inline on mobile
viewports.
- Fixed a parameter resolution bug in layouts/partials/toc.html by checking both .Site.Params.
smartTOC and .Site.Params.article.smartTOC (where it is defined in params.toml).
- Upgraded the TOC scrollspy JavaScript to target all '#TableOfContents' containers on the page,
enabling highlight tracking on both desktop and mobile layouts.
- Documentation Updates (README.md):
- Restored YAML front matter metadata block at the top.
- Added detailed notes on the CI/CD build process and Gitea runner deployment targets
(/srv/www/docs-public and /srv/www/docs-private).
- Added a categorized and sorted "Shortcodes Quick Reference" table at the top of the shortcodes
section (ordered by Custom, Hugo Default, and Blowfish).
- Documented custom shortcodes (Include, Screenshot, Raw HTML) and missing Hugo default
shortcodes (Highlight, Instagram, Param, Ref, Relref, Twitter, Vimeo, Youtube).
- Restructured headings to ensure all sub-shortcodes are H3, removed block break tags (<br/>),
and appended horizontal separators (---) after H2 sections.
This commit is contained in:
170
assets/js/a11y.js
Normal file
170
assets/js/a11y.js
Normal file
@@ -0,0 +1,170 @@
|
||||
window.A11yPanel = (() => {
|
||||
const FEATURES = {
|
||||
disableBlur: {
|
||||
default: false,
|
||||
apply: (enabled) => {
|
||||
document.querySelectorAll("script[data-blur-id]").forEach((script) => {
|
||||
const targetId = script.getAttribute("data-blur-id");
|
||||
const scrollDivisor = Number(script.getAttribute("data-scroll-divisor") || 300);
|
||||
if (typeof setBackgroundBlur === "function") {
|
||||
setBackgroundBlur(targetId, scrollDivisor, enabled, targetId === "menu-blur");
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
disableImages: {
|
||||
default: false,
|
||||
apply: (enabled) => {
|
||||
const image = document.getElementById("background-image");
|
||||
if (image) {
|
||||
image.style.display = enabled ? "none" : "";
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
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; }
|
||||
.group-hover-card-title { text-decoration: underline !important; }
|
||||
.group-hover-card:hover .group-hover-card-title { 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 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}`);
|
||||
}
|
||||
FEATURES[key]?.apply(value);
|
||||
};
|
||||
|
||||
const initPanel = (panelId) => {
|
||||
const prefix = panelId.replace("a11y-panel", "");
|
||||
const current = getSettings();
|
||||
|
||||
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`);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const togglePanel = () => {
|
||||
const panel = document.getElementById(panelId);
|
||||
const overlay = document.getElementById(`${prefix}a11y-overlay`);
|
||||
const toggle = document.getElementById(`${prefix}a11y-toggle`);
|
||||
|
||||
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) {
|
||||
new MutationObserver(() => {
|
||||
const img = document.getElementById("background-image");
|
||||
if (img) img.style.display = "none";
|
||||
}).observe(document, { childList: true, subtree: 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);
|
||||
},
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user