Initialize project with clean ignore rules
This commit is contained in:
138
layouts/_default/list.html
Normal file
138
layouts/_default/list.html
Normal file
@@ -0,0 +1,138 @@
|
||||
{{ define "main" }}
|
||||
{{ .Scratch.Set "scope" "list" }}
|
||||
{{ $enableToc := .Params.showTableOfContents | default (site.Params.list.showTableOfContents | default false) }}
|
||||
{{ $showToc := and $enableToc (in .TableOfContents "<ul") }}
|
||||
|
||||
{{/* Hero */}}
|
||||
{{ if site.Params.list.showHero | default false }}
|
||||
{{ $heroStyle := print "hero/" site.Params.list.heroStyle ".html" }}
|
||||
{{ if templates.Exists ( printf "partials/%s" $heroStyle ) }}
|
||||
{{ partial $heroStyle . }}
|
||||
{{ else }}
|
||||
{{ partial "hero/basic.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Header */}}
|
||||
<header>
|
||||
{{ if .Params.showBreadcrumbs | default (site.Params.list.showBreadcrumbs | default false) }}
|
||||
{{ partial "breadcrumbs.html" . }}
|
||||
{{ end }}
|
||||
<h1 class="mt-5 text-4xl font-extrabold text-neutral-900 dark:text-neutral">{{ .Title }}</h1>
|
||||
<div class="mt-1 mb-2 text-base text-neutral-500 dark:text-neutral-400 print:hidden">
|
||||
{{ partial "article-meta/list.html" (dict "context" . "scope" "single") }}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{/* Description (markdown content) */}}
|
||||
{{ $tocMargin := cond $showToc "mt-12" "mt-0" }}
|
||||
{{ $topClass := cond (hasPrefix site.Params.header.layout "fixed") "lg:top-[140px]" "lg:top-10" }}
|
||||
<section class="{{ $tocMargin }} prose flex max-w-full flex-col dark:prose-invert lg:flex-row">
|
||||
{{ if $showToc }}
|
||||
<div class="order-first px-0 lg:order-last lg:max-w-xs lg:ps-8">
|
||||
<div class="toc ps-5 lg:sticky {{ $topClass }}">
|
||||
{{ if $showToc }}
|
||||
{{ partial "toc.html" . }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="min-w-0 min-h-0 max-w-prose w-full">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{/* Article Grid */}}
|
||||
{{ if gt .Pages 0 }}
|
||||
{{ $cardView := .Params.cardView | default (site.Params.list.cardView | default false) }}
|
||||
{{ $cardViewScreenWidth := .Params.cardViewScreenWidth | default (site.Params.list.cardViewScreenWidth | default false) }}
|
||||
{{ $groupByYear := .Params.groupByYear | default (site.Params.list.groupByYear | default false) }}
|
||||
{{ $orderByWeight := .Params.orderByWeight | default (site.Params.list.orderByWeight | default false) }}
|
||||
{{ $groupByYear := and (not $orderByWeight) $groupByYear }}
|
||||
|
||||
{{ if not $cardView }}
|
||||
<section class="space-y-10 w-full">
|
||||
{{ if not $orderByWeight }}
|
||||
{{ range (.Pages.GroupByDate "2006") }}
|
||||
{{ if $groupByYear }}
|
||||
<h2 class="mt-12 text-2xl font-bold text-neutral-700 first:mt-8 dark:text-neutral-300">
|
||||
{{ .Key }}
|
||||
</h2>
|
||||
{{ end }}
|
||||
{{ range .Pages }}
|
||||
{{ partial "article-link/simple.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ range .Pages.ByWeight }}
|
||||
{{ partial "article-link/simple.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ else }}
|
||||
{{ if $groupByYear }}
|
||||
{{ range (.Pages.GroupByDate "2006") }}
|
||||
{{ if $cardViewScreenWidth }}
|
||||
<div class="relative w-screen max-w-[1600px] px-[30px] start-[calc(max(-50vw,-800px)+50%)]">
|
||||
<h2 class="mt-12 mb-3 text-2xl font-bold text-neutral-700 first:mt-8 dark:text-neutral-300">
|
||||
{{ .Key }}
|
||||
</h2>
|
||||
<section class="w-full grid gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
||||
{{ range .Pages }}
|
||||
{{ partial "article-link/card.html" . }}
|
||||
{{ end }}
|
||||
</section>
|
||||
</div>
|
||||
{{ else }}
|
||||
<h2 class="mt-12 mb-3 text-2xl font-bold text-neutral-700 first:mt-8 dark:text-neutral-300">
|
||||
{{ .Key }}
|
||||
</h2>
|
||||
<section class="w-full grid gap-4 sm:grid-cols-2 md:grid-cols-3">
|
||||
{{ range .Pages }}
|
||||
{{ partial "article-link/card.html" . }}
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ if $cardViewScreenWidth }}
|
||||
<div class="relative w-screen max-w-[1600px] px-[30px] start-[calc(max(-50vw,-800px)+50%)]">
|
||||
<section class="w-full grid gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
||||
{{ if not $orderByWeight }}
|
||||
{{ range (.Pages.GroupByDate "2006") }}
|
||||
{{ range .Pages }}
|
||||
{{ partial "article-link/card.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ range .Pages.ByWeight }}
|
||||
{{ partial "article-link/card.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</section>
|
||||
</div>
|
||||
{{ else }}
|
||||
<section class="w-full grid gap-4 sm:grid-cols-2 md:grid-cols-3">
|
||||
{{ if not $orderByWeight }}
|
||||
{{ range (.Pages.GroupByDate "2006") }}
|
||||
{{ range .Pages }}
|
||||
{{ partial "article-link/card.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ range .Pages.ByWeight }}
|
||||
{{ partial "article-link/card.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<section class="mt-10 prose dark:prose-invert">
|
||||
<p class="py-8 border-t">
|
||||
<em>{{ i18n "list.no_articles" | emojify }}</em>
|
||||
</p>
|
||||
</section>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
82
layouts/_default/single.html
Normal file
82
layouts/_default/single.html
Normal file
@@ -0,0 +1,82 @@
|
||||
{{ define "main" }}
|
||||
{{ .Scratch.Set "scope" "single" }}
|
||||
|
||||
<article class="w-full py-8">
|
||||
{{ if .Params.showHero | default (site.Params.article.showHero | default false) }}
|
||||
{{ partial (printf "hero/%s.html" (.Params.heroStyle | default site.Params.article.heroStyle)) . }}
|
||||
{{ end }}
|
||||
|
||||
<header id="single_header" class="mt-5 max-w-prose mx-auto px-4">
|
||||
<h1 class="mt-0 text-4xl font-extrabold text-neutral-900 dark:text-neutral">{{ .Title | emojify }}</h1>
|
||||
<div class="mt-1 mb-6 text-base text-neutral-500 dark:text-neutral-400 print:hidden">
|
||||
{{ partial "article-meta/basic.html" (dict "context" . "scope" "single") }}
|
||||
</div>
|
||||
{{ if not (.Params.showAuthorBottom | default (site.Params.article.showAuthorBottom | default false)) }}
|
||||
{{ template "SingleAuthor" . }}
|
||||
{{ end }}
|
||||
</header>
|
||||
|
||||
{{/* The Relative Wrapper: Acts as an anchor for the absolute TOC */}}
|
||||
<div class="single-post-wrapper relative mt-8 w-full max-w-7xl mx-auto px-4">
|
||||
|
||||
{{/* Sidebar TOC: Mobile flow (Top), Desktop absolute (Right) */}}
|
||||
{{ if .Params.showTableOfContents | default site.Params.article.showTableOfContents }}
|
||||
<aside id="sidebar-toc" class="w-full mb-8 xl:mb-0">
|
||||
<div class="toc sticky top-24">
|
||||
{{ partial "toc.html" . }}
|
||||
</div>
|
||||
</aside>
|
||||
{{ end }}
|
||||
|
||||
{{/* Main Content: Dead center, uninfluenced by anything */}}
|
||||
<section id="main-content" class="w-full">
|
||||
<div class="article-content prose dark:prose-invert mx-auto">
|
||||
{{ partial "series/series.html" . }}
|
||||
{{ .Content }}
|
||||
|
||||
{{ if .Params.replyByEmail | default site.Params.replyByEmail }}
|
||||
<strong class="block mt-8">
|
||||
<a target="_blank" href="mailto:{{ site.Params.Author.email }}">Reply by Email</a>
|
||||
</strong>
|
||||
{{ end }}
|
||||
|
||||
<div class="mt-10">
|
||||
{{ if (.Params.showAuthorBottom | default (site.Params.article.showAuthorBottom | default false)) }}
|
||||
{{ template "SingleAuthor" . }}
|
||||
{{ end }}
|
||||
{{ partial "series/series-closed.html" . }}
|
||||
{{ partial "sharing-links.html" . }}
|
||||
{{ partial "related.html" . }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="pt-8 mt-12 border-t border-neutral-200 dark:border-neutral-800 max-w-prose mx-auto px-4">
|
||||
{{ partial "article-pagination.html" . }}
|
||||
</footer>
|
||||
</article>
|
||||
{{ end }}
|
||||
|
||||
{{ define "SingleAuthor" }}
|
||||
<div class="max-w-prose">
|
||||
{{ $authorsData := site.Data.authors }}
|
||||
{{ $baseURL := site.BaseURL }}
|
||||
{{ $isAuthorBottom := (.Params.showAuthorBottom | default ( site.Params.article.showAuthorBottom | default false)) }}
|
||||
{{ if not (strings.HasSuffix $baseURL "/") }}{{ $baseURL = delimit (slice $baseURL "/") "" }}{{ end }}
|
||||
|
||||
{{ if .Params.showAuthor | default (site.Params.article.showAuthor | default true) }}
|
||||
{{ partial "author.html" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ range $author := .Page.Params.authors }}
|
||||
{{ $authorData := index $authorsData $author }}
|
||||
{{ if $authorData }}
|
||||
{{ $link := printf "%sauthors/%s/" $baseURL $author }}
|
||||
{{ partial "author-extra.html" (dict "context" . "data" $authorData "link" $link) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<div class="{{ cond $isAuthorBottom "mb-10" "mb-5" }}"></div>
|
||||
</div>
|
||||
{{ end }}
|
||||
8
layouts/index.html
Normal file
8
layouts/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{{ define "main" }}
|
||||
{{ $partial := print "home/" .Site.Params.homepage.layout ".html" }}
|
||||
{{ if templates.Exists ( printf "partials/%s" $partial ) }}
|
||||
{{ partial $partial . }}
|
||||
{{ else }}
|
||||
{{ partial "home/profile.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
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 }}">
|
||||
237
layouts/partials/head.html
Normal file
237
layouts/partials/head.html
Normal file
@@ -0,0 +1,237 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
{{ with .Site.Language.Params.htmlCode | default .Site.LanguageCode }}
|
||||
<meta http-equiv="content-language" content="{{ . }}">
|
||||
{{ end }}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta name="theme-color">
|
||||
|
||||
{{/* Title */}}
|
||||
{{ if .IsHome }}
|
||||
<title>{{ .Site.Title | emojify }}</title>
|
||||
<meta name="title" content="{{ .Site.Title | emojify }}">
|
||||
{{ else }}
|
||||
<title>{{ .Title | emojify }} · {{ .Site.Title | emojify }}</title>
|
||||
<meta name="title" content="{{ .Title | emojify }} · {{ .Site.Title | emojify }}">
|
||||
{{ end }}
|
||||
|
||||
{{/* Metadata */}}
|
||||
{{ with (.Params.Summary | default .Params.Description) | default .Site.Params.description }}
|
||||
<meta name="description" content="{{ . }}">
|
||||
{{ end }}
|
||||
{{ with .Params.Tags | default .Site.Params.keywords }}
|
||||
<meta name="keywords" content="{{ range . }}{{ . }},{{ end }}">
|
||||
{{ end }}
|
||||
{{ with .Site.Params.robots }}
|
||||
<meta name="robots" content="{{ . }}">
|
||||
{{ end }}
|
||||
{{ with .Params.robots }}
|
||||
<meta name="robots" content="{{ . }}">
|
||||
{{ end }}
|
||||
<link rel="canonical" href="{{ .Permalink }}">
|
||||
{{ range .AlternativeOutputFormats }}
|
||||
{{ printf `
|
||||
<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .RelPermalink ($.Site.Title | emojify) |
|
||||
safeHTML
|
||||
}}
|
||||
{{ end }}
|
||||
|
||||
{{/* Me */}}
|
||||
{{ with .Site.Params.Author.name }}
|
||||
<meta name="author" content="{{ . }}">
|
||||
{{ end }}
|
||||
{{ with .Site.Params.Author.links }}
|
||||
{{ range $links := . }}
|
||||
{{ range $name, $url := $links }}
|
||||
{{ if not (strings.HasPrefix $url "mailto:") }}
|
||||
<link href="{{ $url }}" rel="me">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Social */}}
|
||||
{{ template "_internal/opengraph.html" . }}
|
||||
{{ template "_internal/twitter_cards.html" . }}
|
||||
{{/* Use defaultSocialImage if feature image does not exist */}}
|
||||
{{ $featureImage := "" }}
|
||||
{{ $pageImages := .Resources.ByType "image" }}
|
||||
{{ range slice "*featured*" "*cover*" "*thumbnail*" }}
|
||||
{{ if not $featureImage }}
|
||||
{{ $featureImage = $pageImages.GetMatch . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if not $featureImage }}
|
||||
{{ with .Site.Params.defaultSocialImage }}
|
||||
{{ $socialImage := "" }}
|
||||
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
|
||||
{{ $socialImage = resources.GetRemote . }}
|
||||
{{ else }}
|
||||
{{ $socialImage = resources.Get . }}
|
||||
{{ end }}
|
||||
{{ with $socialImage }}
|
||||
<meta name="twitter:image" content="{{ .RelPermalink | absURL }}">
|
||||
<meta property="og:image" content="{{ .RelPermalink | absURL }}">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Site Verification */}}
|
||||
{{ with .Site.Params.verification.google }}
|
||||
<meta name="google-site-verification" content="{{ . }}">
|
||||
{{ end }}
|
||||
{{ with .Site.Params.verification.bing }}
|
||||
<meta name="msvalidate.01" content="{{ . }}">
|
||||
{{ end }}
|
||||
{{ with .Site.Params.verification.pinterest }}
|
||||
<meta name="p:domain_verify" content="{{ . }}">
|
||||
{{ end }}
|
||||
{{ with .Site.Params.verification.yandex }}
|
||||
<meta name="yandex-verification" content="{{ . }}">
|
||||
{{ end }}
|
||||
{{ with .Site.Params.verification.fediverse }}
|
||||
<meta name="fediverse:creator" content="{{ . }}">
|
||||
{{ end }}
|
||||
|
||||
{{ $alg := .Site.Params.fingerprintAlgorithm | default "sha512" }}
|
||||
|
||||
{{/* CSS */}}
|
||||
{{ $cssResources := slice }}
|
||||
{{ $schemeName := .Site.Params.colorScheme | default "blowfish" }}
|
||||
{{ $cssScheme := resources.Get (printf "css/schemes/%s.css" (lower $schemeName)) | default (resources.Get "css/schemes/blowfish.css") }}
|
||||
{{ $cssResources = $cssResources | append $cssScheme }}
|
||||
{{ $cssResources = $cssResources | append (resources.Get "css/compiled/main.css") }}
|
||||
{{ with resources.Get "css/custom.css" }}
|
||||
{{ $cssResources = $cssResources | append . }}
|
||||
{{ end }}
|
||||
{{ if not .Site.Params.disableImageZoom | default true }}
|
||||
{{ $cssResources = $cssResources | append (resources.Get "lib/zoom/style.css") }}
|
||||
{{ end }}
|
||||
{{ $bundleCSS := $cssResources | resources.Concat "css/main.bundle.css" | resources.Minify | resources.Fingerprint $alg }}
|
||||
<link
|
||||
type="text/css"
|
||||
rel="stylesheet"
|
||||
href="{{ $bundleCSS.RelPermalink }}"
|
||||
integrity="{{ $bundleCSS.Data.Integrity }}">
|
||||
|
||||
{{/* JS loaded immediately */}}
|
||||
{{ $jsAppearance := resources.Get "js/appearance.js" | resources.ExecuteAsTemplate "js/appearance.js" . | resources.Minify | resources.Fingerprint $alg }}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{ $jsAppearance.RelPermalink }}"
|
||||
integrity="{{ $jsAppearance.Data.Integrity }}"></script>
|
||||
{{ $enableA11y := .Site.Params.enableA11y | default false }}
|
||||
{{ if $enableA11y }}
|
||||
{{ $jsA11y := resources.Get "js/a11y.js" | resources.Minify | resources.Fingerprint $alg }}
|
||||
<script src="{{ $jsA11y.RelPermalink }}" integrity="{{ $jsA11y.Data.Integrity }}"></script>
|
||||
{{ end }}
|
||||
{{ $showZenMode := .Params.showZenMode | default (.Site.Params.article.showZenMode | default false) }}
|
||||
{{ $shouldIncludeZenMode := or $enableA11y $showZenMode }}
|
||||
{{ if and .IsPage $shouldIncludeZenMode }}
|
||||
{{ $jsZenMode := resources.Get "js/zen-mode.js" | resources.Minify | resources.Fingerprint $alg }}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{ $jsZenMode.RelPermalink }}"
|
||||
integrity="{{ $jsZenMode.Data.Integrity }}"></script>
|
||||
{{ end }}
|
||||
{{ if not .Site.Params.disableImageZoom | default true }}
|
||||
{{ $zoomJS := resources.Get "lib/zoom/zoom.min.umd.js" | resources.Fingerprint $alg }}
|
||||
<script src="{{ $zoomJS.RelPermalink }}" integrity="{{ $zoomJS.Data.Integrity }}"></script>
|
||||
{{ end }}
|
||||
|
||||
{{/* JS deferred */}}
|
||||
{{ $jsResources := slice }}
|
||||
{{ if site.Params.footer.showScrollToTop | default true }}
|
||||
{{ $jsResources = $jsResources | append (resources.Get "js/scroll-to-top.js") }}
|
||||
{{ end }}
|
||||
{{ if .Site.Params.enableSearch | default false }}
|
||||
{{ $jsResources = $jsResources | append (resources.Get "lib/fuse/fuse.min.js") | append (resources.Get "js/search.js") }}
|
||||
{{ end }}
|
||||
{{ if .Site.Params.enableCodeCopy | default false }}
|
||||
{{ $jsResources = $jsResources | append (resources.Get "js/code.js") }}
|
||||
{{ end }}
|
||||
{{ if .Site.Params.rtl | default false }}
|
||||
{{ $jsResources = $jsResources | append (resources.Get "js/rtl.js") }}
|
||||
{{ end }}
|
||||
{{ $jsResources = $jsResources | append (resources.Get "js/button-likes.js") }}
|
||||
{{ $jsResources = $jsResources | append (resources.Get "js/katex-render.js") }}
|
||||
{{ $jsResources = $jsResources | append (resources.Get "js/print-support.js") }}
|
||||
{{ if $jsResources }}
|
||||
{{ $bundleJS := $jsResources | resources.Concat "js/main.bundle.js" | resources.Minify | resources.Fingerprint $alg }}
|
||||
<script
|
||||
defer
|
||||
type="text/javascript"
|
||||
id="script-bundle"
|
||||
src="{{ $bundleJS.RelPermalink }}"
|
||||
integrity="{{ $bundleJS.Data.Integrity }}"
|
||||
data-copy="{{ i18n "code.copy" }}"
|
||||
data-copied="{{ i18n "code.copied" }}"></script>
|
||||
{{ end }}
|
||||
|
||||
{{/* Conditional loaded resources */}}
|
||||
{{ partial "vendor.html" . }}
|
||||
|
||||
{{/* Icons */}}
|
||||
{{ if templates.Exists "partials/favicons.html" }}
|
||||
{{ partialCached "favicons.html" .Site }}
|
||||
{{ else }}
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ "apple-touch-icon.png" | relURL }}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ "favicon-32x32.png" | relURL }}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ "favicon-16x16.png" | relURL }}">
|
||||
<link rel="manifest" href="{{ "site.webmanifest" | relURL }}">
|
||||
{{ end }}
|
||||
|
||||
{{/* Schema */}}
|
||||
{{ partial "schema.html" . }}
|
||||
|
||||
{{/* Analytics */}}
|
||||
{{ if hugo.IsProduction }}
|
||||
{{ partial "analytics/main.html" . }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Extend head - eg. for custom analytics scripts, etc. */}}
|
||||
{{ if templates.Exists "partials/extend-head.html" }}
|
||||
{{ partialCached "extend-head.html" .Site }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Uncached extend head - Example: https://gohugo.io/methods/page/hasshortcode/ */}}
|
||||
{{ if templates.Exists "partials/extend-head-uncached.html" }}
|
||||
{{ partial "extend-head-uncached.html" . }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Firebase */}}
|
||||
{{ with $.Site.Params.firebase }}
|
||||
{{ if isset $.Site.Params "firebase" }}
|
||||
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-firestore.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js"></script>
|
||||
<script>
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: {{ $.Site.Params.firebase.apiKey }},
|
||||
authDomain: {{ $.Site.Params.firebase.authDomain }},
|
||||
projectId: {{ $.Site.Params.firebase.projectId }},
|
||||
storageBucket: {{ $.Site.Params.firebase.storageBucket }},
|
||||
messagingSenderId: {{ $.Site.Params.firebase.messagingSenderId }},
|
||||
appId: {{ $.Site.Params.firebase.appId }},
|
||||
measurementId: {{ $.Site.Params.firebase.measurementId }}
|
||||
};
|
||||
|
||||
var app = firebase.initializeApp(firebaseConfig);
|
||||
var db = firebase.firestore();
|
||||
var auth = firebase.auth();
|
||||
|
||||
</script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Advertisement */}}
|
||||
{{ with .Site.Params.advertisement.adsense }}
|
||||
<meta name="google-adsense-account" content="{{ . }}">
|
||||
<script
|
||||
async
|
||||
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client={{ . }}"
|
||||
crossorigin="anonymous"></script>
|
||||
{{ end }}
|
||||
</head>
|
||||
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 }}
|
||||
1
layouts/shortcodes/rawhtml.html
Normal file
1
layouts/shortcodes/rawhtml.html
Normal file
@@ -0,0 +1 @@
|
||||
{{ .Inner }}
|
||||
4
layouts/shortcodes/video.html
Normal file
4
layouts/shortcodes/video.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<video width="100%" controls preload="metadata">
|
||||
<source src="{{ .Get "src" }}" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
Reference in New Issue
Block a user