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:
2026-06-06 19:26:33 +00:00
Unverified
parent d329fe9dfb
commit 835b118d1a
443 changed files with 33405 additions and 1220 deletions

View File

@@ -1,19 +1,16 @@
<details
open
id="TOCView"
class="toc-right mt-0 overflow-y-auto overscroll-contain bf-scrollbar rounded-lg -ms-5 ps-5 pe-2 block lg:block">
class="toc-right mt-0 overflow-y-auto overscroll-contain bf-scrollbar rounded-lg -ms-5 ps-5 pe-2 hidden lg:block">
<summary
class="block py-1 text-lg font-semibold cursor-pointer bg-neutral-100 text-neutral-800 -ms-5 ps-5 dark:bg-neutral-700 dark:text-neutral-100 lg:hidden">
{{ i18n "article.table_of_contents" }}
</summary>
<div
id="TableOfContents"
class="min-w-[220px] py-2 border-dotted border-s-1 -ms-5 ps-5 dark:border-neutral-600">
{{/* We use Hugo's native TOC generator so it never disappears */}}
{{ .TableOfContents | safeHTML }}
{{ .TableOfContents | emojify }}
</div>
</details>
<details class="toc-inside mt-0 overflow-hidden rounded-lg -ms-5 ps-5 lg:hidden">
<summary
class="py-1 text-lg font-semibold cursor-pointer bg-neutral-100 text-neutral-800 -ms-5 ps-5 dark:bg-neutral-700 dark:text-neutral-100 lg:hidden">
@@ -21,14 +18,15 @@
</summary>
<div
class="py-2 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
{{ .TableOfContents | safeHTML }}
{{ .TableOfContents | emojify }}
</div>
</details>
{{ if .Site.Params.smartTOC }}
{{ if or .Site.Params.smartTOC .Site.Params.article.smartTOC }}
<script>
(function () {
'use strict'
const SCROLL_OFFSET_RATIO = 0.33
const TOC_SELECTOR = '#TableOfContents'
const ANCHOR_SELECTOR = '.anchor'
@@ -37,32 +35,6 @@
const ACTIVE_CLASS = 'active'
let isJumpingToAnchor = false
// --- NEW LOGIC: Prune .no-toc items before initializing SmartTOC ---
function pruneHiddenTOCItems() {
// Find all headers on the page with the 'no-toc' class
const ignoredHeaders = document.querySelectorAll('h1.no-toc, h2.no-toc, h3.no-toc, h4.no-toc, h5.no-toc, h6.no-toc');
ignoredHeaders.forEach(header => {
if (!header.id) return;
// Find matching links in both desktop and mobile TOCs
const linksToRemove = document.querySelectorAll(`.toc-right a[href="#${header.id}"], .toc-inside a[href="#${header.id}"]`);
linksToRemove.forEach(link => {
const li = link.closest('li');
if (li) {
const parentUl = li.parentElement;
li.remove(); // Remove the item from the TOC
// If removing this item leaves an empty list, remove the ul too
if (parentUl && parentUl.children.length === 0) {
parentUl.remove();
}
}
});
});
}
function getActiveAnchorId(anchors, offsetRatio) {
const threshold = window.scrollY + window.innerHeight * offsetRatio
const tocLinks = [...document.querySelectorAll('#TableOfContents a[href^="#"]')]
@@ -88,13 +60,14 @@
return anchors.find(anchor => tocIds.has(anchor.id))?.id || ''
}
function updateTOC({ toc, anchors, links, scrollOffset, collapseInactive }) {
function updateTOC({ tocs, anchors, links, scrollOffset, collapseInactive }) {
const activeId = getActiveAnchorId(anchors, scrollOffset)
if (!activeId) return
links.forEach(link => {
const isActive = link.getAttribute('href') === `#${activeId}`
link.classList.toggle(ACTIVE_CLASS, isActive)
if (collapseInactive) {
const ul = link.closest('li')?.querySelector('ul')
if (ul) ul.style.display = isActive ? '' : 'none'
@@ -102,42 +75,56 @@
})
if (collapseInactive) {
const activeLink = toc.querySelector(`a[href="#${CSS.escape(activeId)}"]`)
let el = activeLink
while (el && el !== toc) {
if (el.tagName === 'UL') el.style.display = ''
if (el.tagName === 'LI') el.querySelector('ul')?.style.setProperty('display', '')
el = el.parentElement
}
tocs.forEach(toc => {
const activeLink = toc.querySelector(`a[href="#${CSS.escape(activeId)}"]`)
let el = activeLink
while (el && el !== toc) {
if (el.tagName === 'UL') el.style.display = ''
if (el.tagName === 'LI') el.querySelector('ul')?.style.setProperty('display', '')
el = el.parentElement
}
})
}
}
function initTOC() {
// 1. Remove the hidden items first
pruneHiddenTOCItems();
const tocs = document.querySelectorAll(TOC_SELECTOR)
if (tocs.length === 0) return
const toc = document.querySelector(TOC_SELECTOR)
if (!toc) return
const collapseInactive = {{ if site.Params.smartTOCHideUnfocusedChildren }}true{{ else }}false{{ end }}
// 2. Query the anchors, explicitly ignoring .no-toc headers so scroll-spy ignores them
const anchors = [...document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]')].filter(a => !a.classList.contains('no-toc'))
const links = [...toc.querySelectorAll(TOC_LINK_SELECTOR)]
const collapseInactive = {{ if or site.Params.smartTOCHideUnfocusedChildren site.Params.article.smartTOCHideUnfocusedChildren }}true{{ else }}false{{ end }}
const anchors = [...document.querySelectorAll(ANCHOR_SELECTOR)]
const links = []
tocs.forEach(toc => links.push(...toc.querySelectorAll(TOC_LINK_SELECTOR)))
if (collapseInactive) {
toc.querySelectorAll(NESTED_LIST_SELECTOR).forEach(ul => ul.style.display = 'none')
tocs.forEach(toc => {
toc.querySelectorAll(NESTED_LIST_SELECTOR).forEach(ul => ul.style.display = 'none')
})
}
links.forEach(link => {
link.addEventListener('click', () => { isJumpingToAnchor = true })
link.addEventListener('click', () => {
isJumpingToAnchor = true
})
})
const config = { toc, anchors, links, scrollOffset: SCROLL_OFFSET_RATIO, collapseInactive }
const config = {
tocs,
anchors,
links,
scrollOffset: SCROLL_OFFSET_RATIO,
collapseInactive
}
window.addEventListener('scroll', () => updateTOC(config), { passive: true })
window.addEventListener('hashchange', () => updateTOC(config), { passive: true })
updateTOC(config)
}
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', initTOC) : initTOC()
document.readyState === 'loading'
? document.addEventListener('DOMContentLoaded', initTOC)
: initTOC()
})()
</script>
{{ end }}
{{ end }}