Finalize framework: Fix submodules and clean artifacts
This commit is contained in:
102
layouts/partials/article-link/card.html
Normal file
102
layouts/partials/article-link/card.html
Normal file
@@ -0,0 +1,102 @@
|
||||
{{/* Used by
|
||||
1. list.html and term.html (when the cardView option is enabled)
|
||||
2. Recent articles template (when the cardView option is enabled)
|
||||
3. Shortcode list.html
|
||||
*/}}
|
||||
{{ $disableImageOptimization := site.Store.Get "disableImageOptimization" }}
|
||||
|
||||
{{ $page := .Page }}
|
||||
{{ $featured := "" }}
|
||||
{{ $featuredURL := "" }}
|
||||
{{ if not .Params.hideFeatureImage }}
|
||||
{{/* frontmatter and resources logic remains the same */}}
|
||||
{{ with $page }}
|
||||
{{ with .Params.featureimage }}
|
||||
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
|
||||
{{ if site.Params.hotlinkFeatureImage }}
|
||||
{{ $featuredURL = . }}
|
||||
{{ else }}
|
||||
{{ $featured = resources.GetRemote . }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ $featured = resources.Get . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if not (or $featured $featuredURL) }}
|
||||
{{ $images := .Resources.ByType "image" }}
|
||||
{{ range slice "*feature*" "*cover*" "*thumbnail*" }}
|
||||
{{ if not $featured }}{{ $featured = $images.GetMatch . }}{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if not (or $featured $featuredURL) }}
|
||||
{{ $default := site.Store.Get "defaultFeaturedImage" }}
|
||||
{{ if $default.url }}
|
||||
{{ $featuredURL = $default.url }}
|
||||
{{ else if $default.obj }}
|
||||
{{ $featured = $default.obj }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if not $featuredURL }}
|
||||
{{ with $featured }}
|
||||
{{ $featuredURL = .RelPermalink }}
|
||||
{{ if not (or $disableImageOptimization (eq .MediaType.SubType "svg")) }}
|
||||
{{ $featuredURL = (.Resize "600x").RelPermalink }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<article
|
||||
class="relative flex flex-col h-full overflow-hidden rounded-lg border border-neutral-300 dark:border-neutral-600">
|
||||
{{ with $featuredURL }}
|
||||
<div class="flex-none relative overflow-hidden thumbnail_card h-40">
|
||||
<img
|
||||
src="{{ . }}"
|
||||
role="presentation"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="not-prose absolute inset-0 w-full h-full object-cover">
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if and .Draft .Site.Params.article.showDraftLabel }}
|
||||
<span class="absolute top-0 right-0 m-2">
|
||||
{{ partial "badge.html" (i18n "article.draft" | emojify) }}
|
||||
</span>
|
||||
{{ end }}
|
||||
|
||||
{{/* Main Content Wrapper - Added flex-grow to push footer down */}}
|
||||
<div class="p-4 flex flex-col flex-grow">
|
||||
<header>
|
||||
<a
|
||||
{{ with $page.Params.externalUrl }}
|
||||
href="{{ . }}" target="_blank" rel="external"
|
||||
{{ else }}
|
||||
href="{{ $page.RelPermalink }}"
|
||||
{{ end }}
|
||||
class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
|
||||
<h2>
|
||||
{{ .Title | emojify }}
|
||||
{{ if .Params.externalUrl }}
|
||||
<span class="cursor-default align-top text-xs text-neutral-400 dark:text-neutral-500">
|
||||
<span class="rtl:hidden">↗</span>
|
||||
<span class="ltr:hidden">↖</span>
|
||||
</span>
|
||||
{{ end }}
|
||||
</h2>
|
||||
</a>
|
||||
</header>
|
||||
<div class="text-sm text-neutral-500 dark:text-neutral-400 mb-2">
|
||||
{{ partial "article-meta/basic.html" . }}
|
||||
</div>
|
||||
|
||||
{{ if .Params.showSummary | default (.Site.Params.list.showSummary | default false) }}
|
||||
{{/* Added line-clamp-3 and flex-grow here */}}
|
||||
<div class="prose dark:prose-invert py-1 line-clamp-3 flex-grow text-sm">
|
||||
{{ .Summary | plainify }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="px-6 pt-4 pb-2"></div>
|
||||
</article>
|
||||
2
layouts/partials/favicons.html
Normal file
2
layouts/partials/favicons.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<link rel="icon" type="image/svg+xml" href="{{ "/icons/icon.svg" | relURL }}">
|
||||
<link rel="apple-touch-icon" href="{{ "/icons/icon.svg" | relURL }}">
|
||||
71
layouts/partials/footer.html
Normal file
71
layouts/partials/footer.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<footer id="site-footer" class="py-10 print:hidden">
|
||||
{{/* Footer menu */}}
|
||||
{{ if .Site.Params.footer.showMenu | default true }}
|
||||
{{ if .Site.Menus.footer }}
|
||||
{{ $onlyIcon := true }}
|
||||
{{ range .Site.Menus.footer }}
|
||||
{{ if .Name }}
|
||||
{{ $onlyIcon = false }}
|
||||
{{ break }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ $navClass := printf "flex flex-row pb-4 text-base font-medium text-neutral-500 dark:text-neutral-400 %s" (cond $onlyIcon "overflow-x-auto py-2" "") }}
|
||||
{{ $ulClass := printf "flex list-none %s" (cond $onlyIcon "flex-row" "flex-col sm:flex-row") }}
|
||||
{{ $liClass := printf "flex mb-1 text-end sm:mb-0 sm:me-7 sm:last:me-0 %s" (cond $onlyIcon "me-4" "") }}
|
||||
<nav class="{{ $navClass }}">
|
||||
<ul class="{{ $ulClass }}">
|
||||
{{ range .Site.Menus.footer }}
|
||||
<li class=" {{ $liClass }}">
|
||||
<a
|
||||
class="decoration-primary-500 hover:underline hover:decoration-2 hover:underline-offset-2 flex items-center"
|
||||
href="{{ .URL }}"
|
||||
title="{{ .Title }}">
|
||||
{{ if .Pre }}
|
||||
<span {{ if and .Pre .Name }}class="mr-1"{{ end }}>
|
||||
{{ partial "icon.html" .Pre }}
|
||||
</span>
|
||||
{{ end }}
|
||||
{{ .Name | markdownify }}
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</nav>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<div class="flex items-center justify-between">
|
||||
{{/* Copyright */}}
|
||||
{{ if .Site.Params.footer.showCopyright | default true }}
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
{{- with replace .Site.Params.copyright "{ year }" now.Year }}
|
||||
{{ . | markdownify }}
|
||||
{{- else }}
|
||||
©
|
||||
{{ now.Format "2006" }}
|
||||
{{ .Site.Params.Author.name | markdownify }}
|
||||
{{- end }}
|
||||
</p>
|
||||
{{ end }}
|
||||
|
||||
{{/* Theme attribution */}}
|
||||
{{ if .Site.Params.footer.showThemeAttribution | default true }}
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
Powered by <a class="hover:underline hover:decoration-primary-400 hover:text-primary-500"
|
||||
href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Your Mom</a>
|
||||
</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ if not .Site.Params.disableImageZoom | default true }}
|
||||
<script>
|
||||
mediumZoom(document.querySelectorAll("img:not(.nozoom)"), {
|
||||
margin: 24,
|
||||
background: "rgba(0,0,0,0.5)",
|
||||
scrollOffset: 0,
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
{{/* Extend footer - eg. for extra scripts, etc. */}}
|
||||
{{ if templates.Exists "partials/extend-footer.html" }}
|
||||
{{ partialCached "extend-footer.html" . }}
|
||||
{{ end }}
|
||||
</footer>
|
||||
126
layouts/partials/home/background.html
Normal file
126
layouts/partials/home/background.html
Normal file
@@ -0,0 +1,126 @@
|
||||
{{ $disableImageOptimization := .Site.Params.disableImageOptimization | default false }}
|
||||
<article class="prose dark:prose-invert max-w-full">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-x-0 bottom-0 h-1/2"></div>
|
||||
<div class="mx-auto max-w-7xl p-0">
|
||||
<div class="relative sm:overflow-hidden">
|
||||
<div class="fixed inset-x-0 top-0 -z-10">
|
||||
{{ $useDefault := true }}
|
||||
{{ $homepageImage := "" }}
|
||||
{{ with .Site.Params.defaultBackgroundImage }}
|
||||
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
|
||||
{{ $homepageImage = resources.GetRemote . }}
|
||||
{{ else }}
|
||||
{{ $homepageImage = resources.Get . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ with .Site.Params.homepage.homepageImage }}
|
||||
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
|
||||
{{ $homepageImage = resources.GetRemote . }}
|
||||
{{ $useDefault = false }}
|
||||
{{ else }}
|
||||
{{ $homepageImage = resources.Get . }}
|
||||
{{ $useDefault = false }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if $homepageImage }}
|
||||
{{ $style := "" }}
|
||||
{{ $defaultPosition := cond $useDefault site.Params.imagePosition false }}
|
||||
{{ with $.Params.imagePosition | default $defaultPosition }}
|
||||
{{ $style = printf "object-position: %s;" . }}
|
||||
{{ end }}
|
||||
<img
|
||||
id="background-image"
|
||||
class="nozoom mt-0 mr-0 mb-0 ml-0 h-[1000px] w-full object-cover"
|
||||
src="{{ $homepageImage.RelPermalink }}"
|
||||
role="presentation"
|
||||
{{ if $style }}style="{{ $style | safeCSS }}"{{ end }}>
|
||||
<div
|
||||
class="from-neutral absolute inset-0 h-[1000px] bg-gradient-to-t to-transparent mix-blend-normal dark:from-neutral-800"></div>
|
||||
<div
|
||||
class="from-neutral absolute inset-0 h-[1000px] bg-gradient-to-t to-neutral-100 opacity-60 mix-blend-normal dark:from-neutral-800 dark:to-neutral-800"></div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="relative flex flex-col items-center justify-center px-1 py-1 text-center">
|
||||
{{ with .Site.Params.Author.image }}
|
||||
{{ $authorImage := "" }}
|
||||
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
|
||||
{{ $authorImage = resources.GetRemote . }}
|
||||
{{ else }}
|
||||
{{ $authorImage = resources.Get . }}
|
||||
{{ end }}
|
||||
{{ if $authorImage }}
|
||||
{{ $final := $authorImage }}
|
||||
{{ $squareImage := $authorImage }}
|
||||
{{ if not (or $disableImageOptimization (eq $authorImage.MediaType.SubType "svg")) }}
|
||||
{{ $final = $authorImage.Fill (print "288x288 q" ( $.Site.Params.Author.imagequality | default "96" )) }}
|
||||
{{ $shortSide := int (math.Min $authorImage.Width $authorImage.Height) }}
|
||||
{{ $squareImage = $authorImage.Crop (printf "%dx%d" $shortSide $shortSide ) }}
|
||||
{{ end }}
|
||||
<img
|
||||
class="mb-2 h-36 w-36 rounded-full"
|
||||
width="144"
|
||||
height="144"
|
||||
alt="{{ $.Site.Params.Author.name | default `Author` }}"
|
||||
src="{{ $final.RelPermalink }}"
|
||||
data-zoom-src="{{ $squareImage.RelPermalink }}">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<h1 class="mb-2 text-4xl font-extrabold text-neutral-800 dark:text-neutral-200">
|
||||
{{ .Site.Params.Author.name | default .Site.Title }}
|
||||
</h1>
|
||||
{{ with .Site.Params.Author.headline }}
|
||||
<h2 class="mt-0 mb-0 text-xl text-neutral-800 dark:text-neutral-300">
|
||||
{{ . | markdownify }}
|
||||
</h2>
|
||||
{{ end }}
|
||||
<div class="mt-3 mb-10 text-2xl">
|
||||
{{ with .Site.Params.Author.links }}
|
||||
<div class="flex flex-wrap">
|
||||
{{ range $links := . }}
|
||||
{{ range $name, $url := $links }}
|
||||
<a
|
||||
class="hover:text-primary-400 text-primary-800 dark:text-primary-200 px-1"
|
||||
href="{{ $url }}"
|
||||
target="_blank"
|
||||
aria-label="{{ $name | title }}"
|
||||
title="{{ $name | title }}"
|
||||
rel="me noopener noreferrer">
|
||||
{{ partial "icon.html" $name }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative flex flex-col w-full px-1 py-1 text-left">
|
||||
<section class="prose dark:prose-invert mx-auto w-full">
|
||||
{{ .Content }}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<section>
|
||||
{{ partial "recent-articles/main.html" . }}
|
||||
</section>
|
||||
|
||||
{{ if .Site.Params.homepage.layoutBackgroundBlur | default false }}
|
||||
<div
|
||||
id="background-blur"
|
||||
class="fixed opacity-0 inset-x-0 top-0 h-full single_hero_background nozoom backdrop-blur-xl bg-neutral-100/75 dark:bg-neutral-800/60"></div>
|
||||
{{ $backgroundBlur := resources.Get "js/background-blur.js" }}
|
||||
{{ $backgroundBlur = $backgroundBlur | resources.Minify | resources.Fingerprint (.Site.Params.fingerprintAlgorithm | default "sha512") }}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{ $backgroundBlur.RelPermalink }}"
|
||||
integrity="{{ $backgroundBlur.Data.Integrity }}"
|
||||
data-blur-id="background-blur"
|
||||
data-image-id="background-image"
|
||||
{{ if $homepageImage }}data-image-url="{{ $homepageImage.RelPermalink }}"{{ end }}></script>
|
||||
{{ end }}
|
||||
143
layouts/partials/toc.html
Normal file
143
layouts/partials/toc.html
Normal file
@@ -0,0 +1,143 @@
|
||||
<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">
|
||||
<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 }}
|
||||
</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 | safeHTML }}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{{ if .Site.Params.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
|
||||
|
||||
// --- 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^="#"]')]
|
||||
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({ toc, 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) {
|
||||
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 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)]
|
||||
|
||||
if (collapseInactive) {
|
||||
toc.querySelectorAll(NESTED_LIST_SELECTOR).forEach(ul => ul.style.display = 'none')
|
||||
}
|
||||
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', () => { isJumpingToAnchor = true })
|
||||
})
|
||||
|
||||
const config = { toc, 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 }}
|
||||
Reference in New Issue
Block a user