- 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.
131 lines
4.4 KiB
HTML
131 lines
4.4 KiB
HTML
<details
|
|
open
|
|
id="TOCView"
|
|
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
|
|
class="min-w-[220px] py-2 border-dotted border-s-1 -ms-5 ps-5 dark:border-neutral-600">
|
|
{{ .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">
|
|
{{ i18n "article.table_of_contents" }}
|
|
</summary>
|
|
<div
|
|
class="py-2 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
|
|
{{ .TableOfContents | emojify }}
|
|
</div>
|
|
</details>
|
|
|
|
{{ 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'
|
|
const TOC_LINK_SELECTOR = 'a[href^="#"]'
|
|
const NESTED_LIST_SELECTOR = 'li ul'
|
|
const ACTIVE_CLASS = 'active'
|
|
let isJumpingToAnchor = false
|
|
|
|
function getActiveAnchorId(anchors, offsetRatio) {
|
|
const threshold = window.scrollY + window.innerHeight * offsetRatio
|
|
const tocLinks = [...document.querySelectorAll('#TableOfContents a[href^="#"]')]
|
|
const tocIds = new Set(tocLinks.map(link => link.getAttribute('href').substring(1)))
|
|
|
|
if (isJumpingToAnchor) {
|
|
for (let i = 0; i < anchors.length; i++) {
|
|
const anchor = anchors[i]
|
|
if (!tocIds.has(anchor.id)) continue
|
|
const top = anchor.getBoundingClientRect().top + window.scrollY
|
|
if (Math.abs(window.scrollY - top) < 100) {
|
|
return anchor.id
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let i = anchors.length - 1; i >= 0; i--) {
|
|
const top = anchors[i].getBoundingClientRect().top + window.scrollY
|
|
if (top <= threshold && tocIds.has(anchors[i].id)) {
|
|
return anchors[i].id
|
|
}
|
|
}
|
|
return anchors.find(anchor => tocIds.has(anchor.id))?.id || ''
|
|
}
|
|
|
|
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'
|
|
}
|
|
})
|
|
|
|
if (collapseInactive) {
|
|
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() {
|
|
const tocs = document.querySelectorAll(TOC_SELECTOR)
|
|
if (tocs.length === 0) return
|
|
|
|
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) {
|
|
tocs.forEach(toc => {
|
|
toc.querySelectorAll(NESTED_LIST_SELECTOR).forEach(ul => ul.style.display = 'none')
|
|
})
|
|
}
|
|
|
|
links.forEach(link => {
|
|
link.addEventListener('click', () => {
|
|
isJumpingToAnchor = true
|
|
})
|
|
})
|
|
|
|
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()
|
|
})()
|
|
</script>
|
|
{{ end }}
|