mirror of
https://github.com/tiennm99/HugoBlox-kit.git
synced 2026-10-06 06:15:13 +00:00
326 lines
14 KiB
HTML
326 lines
14 KiB
HTML
{{ define "main" }}
|
|
|
|
{{ $pub_type_csl := "" }}
|
|
{{ $pub_type_display := "" }}
|
|
{{ if .Params.publication_types }}
|
|
{{ if reflect.IsSlice .Params.publication_types }}
|
|
{{ $pub_type_csl = index .Params.publication_types 0 }}
|
|
{{ $pub_type_display = i18n (printf "pub_%s" (strings.Replace $pub_type_csl "-" "_")) | default (strings.Title $pub_type_csl) }}
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{/* Page Cover */}}
|
|
{{ partial "components/cover.html" . }}
|
|
|
|
<div class="mx-auto flex max-w-screen-xl">
|
|
{{ partial "components/sidebar.html" (dict "context" . "no_sidebar" true) }}
|
|
{{ partial "components/toc.html" . }}
|
|
<article class="w-full break-words flex min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-center pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
|
<main class="w-full min-w-0 max-w-6xl px-6 pt-4 md:px-12">
|
|
|
|
{{ if .Params.show_breadcrumb }}
|
|
<div class="mb-4">
|
|
{{ partial "components/breadcrumb.html" . }}
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{/* Add spacing if cover has overlapping icon */}}
|
|
{{ $cover := partial "functions/get_cover_image.html" . }}
|
|
{{ $has_overlap_icon := false }}
|
|
{{ if $cover }}
|
|
{{ $icon_config := .Params.cover.icon | default (dict) }}
|
|
{{ if or $icon_config.name $icon_config.emoji }}
|
|
{{ $icon_position := $icon_config.position | default "overlap" }}
|
|
{{ if eq $icon_position "overlap" }}
|
|
{{ $has_overlap_icon = true }}
|
|
{{ end }}
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
<h1 class="{{ if $has_overlap_icon }}mt-12{{ else }}mt-2{{ end }} text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100" data-pagefind-meta="title" data-pagefind-body>
|
|
{{/* Inline icon before title if specified */}}
|
|
{{ if $cover }}
|
|
{{ $icon_config := .Params.cover.icon | default (dict) }}
|
|
{{ if or $icon_config.name $icon_config.emoji }}
|
|
{{ $icon_position := $icon_config.position | default "overlap" }}
|
|
{{ if eq $icon_position "inline" }}
|
|
<span class="inline-flex items-center gap-3">
|
|
{{ if $icon_config.emoji }}
|
|
<span class="text-4xl">{{ $icon_config.emoji }}</span>
|
|
{{ else if $icon_config.name }}
|
|
<span class="w-10 h-10">
|
|
{{ partial "components/icon.html" (dict "name" $icon_config.name "attributes" "class=\"w-full h-full\"") }}
|
|
</span>
|
|
{{ end }}
|
|
<span>{{- .Title -}}</span>
|
|
</span>
|
|
{{ else }}
|
|
{{- .Title -}}
|
|
{{ end }}
|
|
{{ else }}
|
|
{{- .Title -}}
|
|
{{ end }}
|
|
{{ else }}
|
|
{{- .Title -}}
|
|
{{ end }}
|
|
</h1>
|
|
|
|
{{ $display_date := .Params.event_start | default .Date }}
|
|
<div class="mt-4 mb-16">
|
|
<div class="text-gray-500 dark:text-gray-300 text-sm flex items-center flex-wrap gap-y-2">
|
|
{{- if $display_date | and (not .Params.hide_date) -}}
|
|
<span class="mr-1">{{- (time $display_date) | time.Format (site.Params.hugoblox.locale.date_format | default ":date_long") -}}</span>
|
|
{{- if .Params.authors }}<span class="mx-1">·</span>{{ end -}}
|
|
{{- end -}}
|
|
|
|
{{/* Set Alpine.js flag if author notes exist */}}
|
|
{{ if isset .Params "author_notes" }}
|
|
{{ .Store.Set "has_alpine" true }}
|
|
{{ end }}
|
|
|
|
{{ range $i, $slug := .Params.authors }}
|
|
{{ $profile := partial "functions/get_author_profile" $slug }}
|
|
{{ $avatar := $profile.avatar }}
|
|
{{ if and $i (not $avatar) }}<span class="mr-1">,</span>{{ end }}
|
|
<div class="group inline-flex items-center text-current gap-x-1.5 mx-1">
|
|
{{ with $avatar }}
|
|
{{ $avatar_32 := .Process "Fill 32x32 Center webp" }}
|
|
<img src="{{ $avatar_32.RelPermalink }}"
|
|
width="16"
|
|
height="16"
|
|
alt="{{ $profile.title }}"
|
|
class="inline-block h-4 w-4 rounded-full border border-current"
|
|
loading="lazy" />
|
|
{{ end }}
|
|
<div>{{ $profile.title }}</div>
|
|
</div>
|
|
{{/* Add author note tooltip if available */}}
|
|
{{- if isset $.Params "author_notes" -}}
|
|
{{- with (index $.Params.author_notes $i) -}}
|
|
<span class="relative inline-block ml-1" x-data="{ tooltip: false }">
|
|
<button
|
|
@mouseenter="tooltip = true"
|
|
@mouseleave="tooltip = false"
|
|
@click="tooltip = !tooltip"
|
|
class="author-notes text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-200 transition-colors cursor-help"
|
|
data-tooltip="{{.}}"
|
|
aria-label="{{.}}"
|
|
type="button"
|
|
>
|
|
<svg class="inline-block w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</button>
|
|
<div
|
|
x-show="tooltip"
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0 transform scale-95"
|
|
x-transition:enter-end="opacity-100 transform scale-100"
|
|
x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100 transform scale-100"
|
|
x-transition:leave-end="opacity-0 transform scale-95"
|
|
@click.away="tooltip = false"
|
|
class="absolute z-50 bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 text-sm text-white bg-gray-900 dark:bg-gray-700 rounded-lg shadow-lg whitespace-nowrap"
|
|
x-cloak
|
|
>
|
|
{{.}}
|
|
<div class="absolute top-full left-1/2 transform -translate-x-1/2 -mt-1 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900 dark:border-t-gray-700"></div>
|
|
</div>
|
|
</span>
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{ end }}
|
|
|
|
{{ if ne .Params.reading_time false }}
|
|
<span class="mx-1">·</span>
|
|
<span class="mx-1">
|
|
{{ .ReadingTime }} {{ i18n "minute_read" }}
|
|
</span>
|
|
{{ end }}
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
{{ partial "page_links_div.html" . }}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{{ $featured := partial "functions/get_featured_image.html" . }}
|
|
{{/* Safely extract image parameters - handle case where image is not a map */}}
|
|
{{ $image_params := dict }}
|
|
{{ if and .Params.image (reflect.IsMap .Params.image) }}
|
|
{{ $image_params = .Params.image }}
|
|
{{ end }}
|
|
{{/* Featured image layout */}}
|
|
{{ if and $featured (not $image_params.preview_only) }}
|
|
{{/* Fit image within max size. */}}
|
|
{{ $image := $featured }}
|
|
|
|
{{/* Determine image placement. */}}
|
|
{{ $placement := $image_params.placement | default 1 }}{{/* Default to full column width. */}}
|
|
{{/* Configure responsive processing based on placement */}}
|
|
{{ $image_container := "" }}
|
|
{{ $responsive_sizes := slice }}
|
|
{{ $sizes_attr := "" }}
|
|
{{ if eq $placement 2}}
|
|
{{ $image_container = "container" }}
|
|
{{ $responsive_sizes = slice 480 768 1024 1200 1600 }}
|
|
{{ $sizes_attr = "(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1200px) 80vw, 1200px" }}
|
|
{{else if eq $placement 3}}
|
|
{{ $image_container = "container-fluid" }}
|
|
{{ $responsive_sizes = slice 768 1024 1366 1920 2560 }}
|
|
{{ $sizes_attr = "(max-width: 768px) 100vw, (max-width: 1366px) 90vw, (max-width: 1920px) 80vw, 2560px" }}
|
|
{{else}}
|
|
{{ $image_container = "article-container" }}
|
|
{{ $responsive_sizes = slice 320 480 640 720 960 }}
|
|
{{ $sizes_attr = "(max-width: 480px) 100vw, (max-width: 640px) 90vw, (max-width: 720px) 80vw, 720px" }}
|
|
{{end}}
|
|
|
|
{{/* Process featured image using utility with placement-specific constraints */}}
|
|
{{ if strings.Contains $featured.MediaType.SubType "svg" }}
|
|
{{/* SVGs cannot be processed by Hugo (Fit/Fill). Render directly. */}}
|
|
<div class="article-header {{$image_container}} featured-image-wrapper mt-4 mb-16">
|
|
<div style="position: relative">
|
|
<img src="{{ $featured.RelPermalink }}"
|
|
data-pagefind-meta="image[src]"
|
|
alt="{{ with $image_params.alt_text }}{{.}}{{ end }}"
|
|
class="featured-image"
|
|
fetchpriority="high">
|
|
{{ with $image_params.caption }}<span class="article-header-caption">{{ . | markdownify | emojify }}</span>{{ end }}
|
|
</div>
|
|
</div>
|
|
{{ else if ne $featured.MediaType.SubType "gif" }}
|
|
{{/* Pre-fit image to placement constraints, then apply responsive processing */}}
|
|
{{ $fitted_image := $featured }}
|
|
{{ if eq $placement 2}}
|
|
{{ $fitted_image = $featured.Fit "1200x2500" }}
|
|
{{else if eq $placement 3}}
|
|
{{ $fitted_image = $featured.Fit "2560x2560" }}
|
|
{{else}}
|
|
{{ $fitted_image = $featured.Fit "720x2500" }}
|
|
{{end}}
|
|
|
|
{{ $responsive := partial "functions/process_responsive_image.html" (dict
|
|
"image" $fitted_image
|
|
"mode" "responsive"
|
|
"sizes" $responsive_sizes
|
|
) }}
|
|
|
|
{{/* Featured image - use fitted image dimensions for container, responsive for srcset */}}
|
|
<div class="article-header {{$image_container}} featured-image-wrapper mt-4 mb-16" style="max-width: {{$fitted_image.Width}}px; max-height: {{$fitted_image.Height}}px;">
|
|
<div style="position: relative">
|
|
<img srcset="{{ $responsive.srcset }}"
|
|
sizes="{{ $sizes_attr }}"
|
|
src="{{ $responsive.fallback.RelPermalink }}"
|
|
data-pagefind-meta="image[src]"
|
|
width="{{ $fitted_image.Width }}"
|
|
height="{{ $fitted_image.Height }}"
|
|
alt="{{ with $image_params.alt_text }}{{.}}{{ end }}"
|
|
class="featured-image"
|
|
fetchpriority="high">
|
|
{{ with $image_params.caption }}<span class="article-header-caption">{{ . | markdownify | emojify }}</span>{{ end }}
|
|
</div>
|
|
</div>
|
|
{{ else }}
|
|
{{/* Handle GIF - apply same placement constraints without responsive processing */}}
|
|
{{ $image := $featured }}
|
|
{{ if eq $placement 2}}
|
|
{{ $image = $featured.Fit "1200x2500" }}
|
|
{{else if eq $placement 3}}
|
|
{{ $image = $featured.Fit "2560x2560" }}
|
|
{{else}}
|
|
{{ $image = $featured.Fit "720x2500" }}
|
|
{{end}}
|
|
<div class="article-header {{$image_container}} featured-image-wrapper mt-4 mb-16" style="max-width: {{$image.Width}}px; max-height: {{$image.Height}}px;">
|
|
<div style="position: relative">
|
|
<img src="{{ $image.RelPermalink }}"
|
|
data-pagefind-meta="image[src]"
|
|
width="{{ $image.Width }}"
|
|
height="{{ $image.Height }}"
|
|
alt="{{ with $image_params.alt_text }}{{.}}{{ end }}"
|
|
class="featured-image"
|
|
fetchpriority="high">
|
|
{{ with $image_params.caption }}<span class="article-header-caption">{{ . | markdownify | emojify }}</span>{{ end }}
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
{{end}}
|
|
|
|
{{/* EVENT / PUBLICATION METADATA */}}
|
|
{{ if .Params.abstract | or (eq .Type "publications") | or (eq .Type "events") }}
|
|
<div class="max-w-prose grid grid-cols-1 md:grid-cols-[200px_auto] gap-4 my-6">
|
|
|
|
{{ if .Params.abstract }}
|
|
<div class="font-bold text-2xl">{{ i18n "abstract" }}</div>
|
|
<div>{{ .Params.abstract | markdownify }}</div>
|
|
{{ end }}
|
|
|
|
{{/* If the type is Uncategorized, hide the type. */}}
|
|
{{ if $pub_type_display }}
|
|
<div class="font-bold text-2xl">{{ i18n "publication_type" }}</div>
|
|
<div>
|
|
{{ if and .Params.publication_types (gt (len .Params.publication_types) 0) }}
|
|
<a href="{{ range first 1 (.GetTerms "publication_types") }}{{.RelPermalink}}{{end}}">
|
|
{{ $pub_type_display }}
|
|
</a>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{ if .Params.publication }}
|
|
<div class="font-bold text-2xl">{{ i18n "publication" }}</div>
|
|
<div>{{ .Params.publication | markdownify }}</div>
|
|
{{ end }}
|
|
|
|
{{ if eq .Type "events" }}
|
|
<div class="font-bold text-2xl">{{ i18n "date" }}</div>
|
|
<div>
|
|
{{ partial "functions/get_event_dates" . }}
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{ if .Params.event_name }}
|
|
<div class="font-bold text-2xl">{{ i18n "event" }}</div>
|
|
<div>
|
|
{{ with .Params.event_url }}<a href="{{ . }}" target="_blank" rel="noopener">{{ end }}
|
|
{{ .Params.event_name | markdownify }}
|
|
{{ if .Params.event_url }}</a>{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{ if .Params.location }}
|
|
<div class="font-bold text-2xl">{{ i18n "location" }}</div>
|
|
<div>
|
|
<p>{{ .Params.location | markdownify }}</p>
|
|
{{ if .Params.address }}
|
|
<p>{{partial "functions/get_address" (dict "root" . "address" .Params.address) }}</p>
|
|
{{end}}
|
|
</div>
|
|
{{ end }}
|
|
|
|
</div>
|
|
{{ end }}
|
|
|
|
<div class="prose prose-slate lg:prose-xl dark:prose-invert" data-pagefind-body>
|
|
{{ with .Section }}
|
|
<span data-pagefind-filter="type" style="display:none;">{{ . }}</span>
|
|
{{ end }}
|
|
{{ with .Params.categories }}
|
|
<span data-pagefind-filter="category" style="display:none;">{{ index . 0 }}</span>
|
|
{{ end }}
|
|
{{ .Content }}
|
|
</div>
|
|
|
|
{{ partial "components/last-edited.html" . }}
|
|
|
|
<div class="container mx-auto prose prose-slate lg:prose-xl dark:prose-invert mt-5">
|
|
{{ .Scratch.Set "invert_pager" true }}
|
|
{{ partial "page_footer" . }}
|
|
</div>
|
|
|
|
</main>
|
|
</article>
|
|
</div>
|
|
{{ end }}
|