hugo 主题 hextra 个性化定制记录

2025-08-31 20:00:09

本文主要记录 hugo 主题 hextra v0.11.0 的个性化定制记录,包括:首页显示博客,搜索优化,代码高亮优化,以及页面调整;

hextra 首页展示 blog

hextra 主题的首页展示的是自定义内容,但我更希望直接展示 blog,在 layouts 下新增:hextra-home.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{{ define "main" }}
  {{- $readMore := (T "readMore") | default "Read more →" -}}
  <div class="hx-mx-auto hx-flex {{ partial `utils/page-width` . }}"></div>
    {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" false) }}
    <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
      <main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
        {{ partial "breadcrumb.html" (dict "page" . "enable" false) }}
        <br class="hx:mt-1.5 hx:text-sm" />
        {{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
        <div class="content">{{ .Content }}</div>
        {{ $paginator := .Paginate (sort (where .Site.RegularPages "Section" "blog") "Date" "desc") }}
        {{- range $paginator.Pages }}
          <div class="hx:mb-10">
            <h3><a style="color: inherit; text-decoration: none;" class="hx:block hx:font-semibold hx:mt-8 hx:text-2xl " href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
            {{ if site.Params.blog.list.displayTags }}
              <div class="hx:text-sm hx:leading-7">
                {{ partial "tags.html" (dict "context" .) }}
              </div>
            {{ end }}
            <p class="hx:opacity-80 hx:mt-4 hx:leading-7">{{- partial "utils/page-description" . -}}</p>
            <p class="hx:opacity-80 hx:mt-1 hx:leading-7">
              <a class="hx:text-[color:hsl(var(--primary-hue),100%,50%)] hx:underline hx:underline-offset-2 hx:decoration-from-font" href="{{ .RelPermalink }}">
                {{- $readMore -}}
              </a>
            </p>
            <p class="hx:opacity-50 hx:text-sm hx:mt-4 hx:leading-7">{{ partial "utils/format-date" .Date }}</p>
          </div>
        {{ end -}}
        
        {{- if gt $paginator.TotalPages 1 -}}
          {{ partial "components/blog-pager.html" . }}
        {{- end -}}
      </main>
    </article>
    <div class="hx:max-xl:hidden hx:h-0 hx:w-64 hx:shrink-0"></div>
  </div>
{{- end -}}

该文件来自主题 layouts/blog/list.html 我只改了第 3, 4, 11, 31 行,第 3,4 行原始内容如下:

1
2
<div class="hx:mx-auto hx:flex hextra-max-page-width">
  {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}

我改为了:

1
2
<div class="hx-mx-auto hx-flex {{ partial `utils/page-width` . }}"></div>
  {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" false) }}

我删除了原始的第 11-13 行,新增了:

{{ $paginator := .Paginate (sort (where .Site.RegularPages "Section" "blog") "Date" "desc") }}

原始 11-13 行内容如下:

1
2
3
{{- $pages := partial "utils/sort-pages" (dict "page" . "by" site.Params.blog.list.sortBy "order" site.Params.blog.list.sortOrder) -}}
{{- $pagerSize := site.Params.blog.list.pagerSize | default 10 -}}
{{- $paginator := .Paginate $pages $pagerSize -}}

原始的第 33 行如下:

{{ partial "components/blog-pager.html" $paginator }}

我改为了:

{{ partial "components/blog-pager.html" . }}

components/blog-pager.html 内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{{ $pag := .Paginator }}
{{ $tps := $pag.TotalPages }}

{{ $begin := sub $pag.PageNumber  4}}
{{ $.Scratch.Set "begin" $begin }}
{{ $end := add $pag.PageNumber  4}}
{{ $.Scratch.Set "end" $end }}

{{ if lt $begin 0}}
  {{ $end := sub $end $begin }}
  {{ $.Scratch.Set "end" $end }}
{{ end}}
{{ $end := $.Scratch.Get "end"}}

{{ $over := sub  $tps $end }}

{{ if lt $over 0}}
  {{ $begin := add $begin $over}}
  {{ $.Scratch.Set "begin" $begin }}
{{ end }}
{{ $begin := $.Scratch.Get "begin"}}
<nav class="pagination">
  {{ if $pag.HasPrev }}
  <a class="extend prev" rel="prev" href="{{ $pag.Prev.URL  }}">
    <i class="fa fa-angle-left"></i>
  </a>
  {{ end }}
  {{ range $p := $pag.Pagers }}
      {{ if and (ge $p.PageNumber $begin) (le $p.PageNumber $end) }}
      {{ if eq $p $pag }}
        <span class="page-number current">{{ $p.PageNumber }}</span>
      {{ else }}
        <a class="page-number" href="{{ $p.URL  }}">{{ $p.PageNumber }}</a>
      {{ end }}
  {{ end }}
  {{ if $pag.HasNext }}
  <a class="extend next" rel="next" href="{{ $pag.Next.URL }}">
    <i class="fa fa-angle-right"></i>
  </a>
  {{ end }}
</nav>

该文件来自 hugo-next-theme 主题,assets/css/custom.css 里需要新增:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.pagination {
    border-top: 1px solid #eee;
    margin: 120px 0 0;
    text-align: center;
}

.algolia-pagination .current .page-number, .pagination .page-number.current {
    background: #ccc;
    border-color: #ccc;
    color: var(--content-bg-color);
}

.pagination .prev, .pagination .next, .pagination .page-number {
    border-bottom: 0;
    border-top: 1px solid #eee;
    transition: border-color 0.2s ease-in-out;
}

.pagination .space, .pagination .prev, .pagination .next, .pagination .page-number {
    display: inline-block;
    margin: -1px 10px 0;
    padding: 0 10px;
}

官方版本博客的分页展示只有下一页、上一页的导航,上述版本来自 next 主题,样式更美。

最后 utils/page-width 来自官方低版本,内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{{/* Get page width from site configuration */}}

{{/* Default page width */}}
{{- $pageWidth := "" -}}

{{/* Get page width setting from page front matter or site params */}}
{{ with .Params.width -}}
  {{ $pageWidth = . -}}
{{ else -}}
  {{ with .Site.Params.page.width -}}
    {{ $pageWidth = . -}}
  {{ end -}}
{{ end -}}

{{- with $pageWidth -}}
  {{ if eq . "wide" -}}
    {{ $pageWidth = "hx-max-w-[90rem]" -}}
  {{ else if eq . "full" -}}
    {{ $pageWidth = "max-w-full" -}}
  {{ else -}}
    {{ $pageWidth = "hx-max-w-screen-xl" -}}
  {{ end -}}
{{ else -}}
  {{ $pageWidth = "hx-max-w-screen-xl" -}}
{{ end -}}

{{ return $pageWidth }}

hextra 博客页面调整

新增 layouts/blog/list.html 修改为空内容即可,避免重复生成博客列表;
新增 layouts/blog/single.html 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{{ define "main" }}
  <div class="hx:mx-auto hx:flex hextra-max-page-width">
    {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" false) }}
    {{ partial "toc.html" . }}
    <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
      <main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
        <!-- {{ partial "breadcrumb.html" (dict "page" . "enable" true) }} -->
        {{ if .Title }}<h1 class="hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
        {{ if site.Params.blog.list.displayTags }}
            <div class="hx:text-sm hx:leading-7">
              {{ partial "tags.html" (dict "context" .) }}
            </div>
        {{ end }}
        <div class="hx:mt-4 hx:mb-16 hx:text-gray-500 hx:dark:text-gray-400 hx:text-sm hx:flex hx:items-center hx:flex-wrap hx:gap-y-2">
          {{- with $date := .Date }}<span class="hx:mr-1">{{ partial "utils/format-date" $date }}</span>{{ end -}}
          {{- $lazyLoading := site.Params.enableImageLazyLoading | default true -}}
          {{ if and .Date .Params.authors }}<span class="hx:mx-1">·</span>{{ end -}}
          {{- with $.Params.authors -}}
            {{- range $i, $author := . -}}
              {{- if reflect.IsMap $author -}}
                {{- if and $i (not $author.image) }}<span class="hx:mr-1">,</span>{{ end -}}
                <a
                  {{ with $author.link }}href="{{ . }}" target="_blank"{{ end }}
                  class="hx:group hx:inline-flex hx:items-center hx:text-current hx:gap-x-1.5 hx:mx-1"
                  {{ with $author.name }}title="{{ . }}"{{ end }}
                >
                  {{- with $image := $author.image }}
                    {{- $isLocal := not (urls.Parse $image).Scheme -}}
                    {{- $startsWithSlash := hasPrefix $image "/" -}}
                    {{- if and $isLocal $startsWithSlash }}
                      {{- $image = (relURL (strings.TrimPrefix "/" $image)) -}}
                    {{ end -}}
                    <img src="{{ $image | safeURL }}" alt="{{ $author.name }}" class="hx:inline-block hx:h-4 hx:w-4 hx:rounded-full" {{ if $lazyLoading }}loading="lazy"{{ end }} />
                  {{ end -}}
                  <div class="hx:group-hover:underline">{{ $author.name }}</div>
                </a>
              {{- else -}}
                {{- if $i }}<span class="hx:mr-1">,</span>{{ end -}}<span class="hx:mx-1">{{ $author }}</span>
              {{- end -}}
            {{- end -}}
          {{- end -}}
        </div>
        <div class="content">
          {{ .Content }}
        </div>
        {{- partial "components/last-updated.html" . -}}
        {{- if (site.Params.blog.article.displayPagination | default true) -}}
          {{- .Scratch.Set "reversePagination" (.Params.reversePagination | default true) -}}
          {{- partial "components/pager.html" . -}}
        {{ end }}
        {{- partial "components/comments.html" . -}}
      </main>
    </article>
  </div>
{{ end }}

我主要改了 3、7 两行,新增了 9-13 行;第 3 行原始内容为:

{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}

我将第二个 true 改为了 false
第 7 行我直接注释掉了;
9-13 行为显示 tag,我比较喜欢这个功能;

hextra docs 页面微调

新增 layouts/docs/single.html 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{{ define "main" }}
  <div class='hx:mx-auto hx:flex hextra-max-page-width'>
    {{ partial "sidebar.html" (dict "context" .) }}
    {{ partial "toc.html" . }}
    <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
      <main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
        {{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
        <div class="content" style="margin-top: 1.5em;">
          {{ if .Title }}<h1>{{ .Title }}</h1>{{ end }}
          {{ .Content }}
        </div>
        {{ partial "components/last-updated.html" . }}
        {{ partial "components/pager.html" . }}
        {{ partial "components/comments.html" . }}
      </main>
    </article>
  </div>
{{ end }}

原始内容第 8 行没有 style,导致 title 和面包屑导航距离太近;

hextra 手机 sidebar 调整

由于魔改的缘故,手机上面,sidebar 不显示导航,我改了 layouts/partials/sidebar.html, 修改后如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
{{- $context := .context -}}

{{- $disableSidebar := .disableSidebar | default false -}}
{{- $displayPlaceholder := .displayPlaceholder | default false -}}

{{- $sidebarClass := cond $disableSidebar (cond $displayPlaceholder "hx:md:hidden hx:xl:block" "hx:md:hidden") "hx:md:sticky" -}}

{{- $navRoot := cond (eq site.Home.Type "docs") site.Home $context.FirstSection -}}
{{- $pageURL := $context.RelPermalink -}}

{{/* EXPERIMENTAL */}}
{{- if .context.Params.sidebar.hide -}}
  {{- $disableSidebar = true -}}
  {{- $displayPlaceholder = true -}}
{{- end -}}


<aside class="hextra-sidebar-container hx:flex hx:flex-col hx:print:hidden hx:md:top-16 hx:md:shrink-0 hx:md:w-64 hx:md:self-start hx:max-md:[transform:translate3d(0,-100%,0)] {{ $sidebarClass }}">
  <!-- Search bar on small screen -->
  <div class="hx:px-4 hx:pt-4 hx:md:hidden">
    {{ partial "search.html" }}
  </div>
  <div class="hextra-scrollbar hx:overflow-y-auto hx:overflow-x-hidden hx:p-4 hx:grow hx:md:h-[calc(100vh-var(--navbar-height)-var(--menu-height))]">
    <ul class="hx:flex hx:flex-col hx:gap-1 hx:md:hidden">
      <!-- Nav -->
      {{ template "sidebar-main" (dict "context" site.Home "pageURL" $pageURL "page" $context "toc" true) -}}
      {{ template "sidebar-footer" }}
      {{ template "sidebar-mobile" }}
    </ul>

    <!-- Sidebar on large screen -->
    {{- if $disableSidebar -}}
      {{- if $displayPlaceholder }}<div class="hx:max-xl:hidden hx:h-0 hx:w-64 hx:shrink-0"></div>{{ end -}}
      {{ .context.Scratch.Set "enableFooterSwitches" true }}
    {{- else -}}
      <ul class="hx:flex hx:flex-col hx:gap-1 hx:max-md:hidden">
        {{ template "sidebar-main" (dict "context" $navRoot "page" $context  "pageURL" $pageURL) }}
        {{ template "sidebar-footer" }}
      </ul>
    {{ end -}}
  </div>
  {{/* Hide theme switch when sidebar is disabled */}}
  {{ $switchesClass := cond $disableSidebar "hx:md:hidden" "" -}}
  {{ $displayThemeToggle := (site.Params.theme.displayToggle | default true) -}}

  {{ if or hugo.IsMultilingual $displayThemeToggle }}
    <div class="{{ $switchesClass }} {{ with hugo.IsMultilingual }}hx:justify-end{{ end }} hx:sticky hx:bottom-0 hx:max-h-(--menu-height) hx:bg-white hx:dark:bg-dark hx:mx-4 hx:py-4 hx:shadow-[0_-12px_16px_#fff] hx:flex hx:items-center hx:gap-2 hx:border-gray-200 hx:dark:border-neutral-800 hx:dark:shadow-[0_-12px_16px_#111] hx:contrast-more:border-neutral-400 hx:contrast-more:shadow-none hx:contrast-more:dark:shadow-none hx:border-t" data-toggle-animation="show">
      {{- with hugo.IsMultilingual -}}
        {{- partial "language-switch" (dict "context" $context "grow" true) -}}
        {{- with $displayThemeToggle }}{{ partial "theme-toggle" (dict "hideLabel" true "location" "bottom-right") }}{{ end -}}
      {{- else -}}
        {{- with $displayThemeToggle -}}
          <div class="hx:flex hx:grow hx:flex-col">{{ partial "theme-toggle" }}</div>
        {{- end -}}
      {{- end -}}
    </div>
  {{- end -}}
</aside>

{{- define "sidebar-main" -}}
  {{ template "sidebar-tree" (dict "context" .context "level" 0 "page" .page "pageURL" .pageURL "toc" (.toc | default false)) }}
{{- end -}}

{{- define "sidebar-tree" -}}
  {{- if ge .level 4 -}}
    {{- return -}}
  {{- end -}}

  {{- $context := .context -}}
  {{- $page := .page }}
  {{- $pageURL := .page.RelPermalink -}}
  {{- $level := .level -}}
  {{- $toc := .toc | default false -}}

  {{- with $items := union .context.RegularPages .context.Sections -}}
    {{- $items = where $items "Params.sidebar.exclude" "!=" true -}}
    {{- if eq $level 0 -}}
      {{- range $items.ByWeight }}
        {{- if .Params.sidebar.separator -}}
          <li class="[word-break:break-word] hx:mt-5 hx:mb-2 hx:px-2 hx:py-1.5 hx:text-sm hx:font-semibold hx:text-gray-900 hx:first:mt-0 hx:dark:text-gray-100">
            <span class="hx:cursor-default">{{ partial "utils/title" . }}</span>
          </li>
        {{- else -}}
          {{- $active := eq $pageURL .RelPermalink -}}
          {{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }}
          <li class="{{ if $shouldOpen }}open{{ end }}">
            {{- $linkTitle := partial "utils/title" . -}}
            {{- template "sidebar-item-link" dict "context" . "active" $active "title" $linkTitle "link" .RelPermalink -}}
            {{- if and $toc $active -}}
              {{- template "sidebar-toc" dict "page" . -}}
            {{- end -}}
            {{- template "sidebar-tree" dict "context" . "page" $page "pageURL" $pageURL "level" (add $level 1) "toc" $toc -}}
          </li>
        {{- end -}}
      {{- end -}}
    {{- else -}}
      <div class="hx:ltr:pr-0 hx:overflow-hidden">
        <ul class='hx:relative hx:flex hx:flex-col hx:gap-1 hx:before:absolute hx:before:inset-y-1 hx:before:w-px hx:before:bg-gray-200 hx:before:content-[""] hx:ltr:ml-3 hx:ltr:pl-3 hx:ltr:before:left-0 hx:rtl:mr-3 hx:rtl:pr-3 hx:rtl:before:right-0 hx:dark:before:bg-neutral-800'>
          {{- range $items.ByWeight }}
            {{- $active := eq $pageURL .RelPermalink -}}
            {{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }}
            {{- $linkTitle := partial "utils/title" . -}}
            <li class="hx:flex hx:flex-col {{ if $shouldOpen }}open{{ end }}">
              {{- template "sidebar-item-link" dict "context" . "active" $active "title" $linkTitle "link" .RelPermalink -}}
              {{- if and $toc $active -}}
                {{ template "sidebar-toc" dict "page" . }}
              {{- end }}
              {{ template "sidebar-tree" dict "context" . "page" $page "pageURL" $pageURL "level" (add $level 1) "toc" $toc }}
            </li>
          {{- end -}}
        </ul>
      </div>
    {{- end -}}
  {{- end }}
{{- end -}}

{{- define "sidebar-toc" -}}
  {{ $page := .page }}
  {{ with $page.Fragments.Headings }}
    <ul class='hx:flex hx:flex-col hx:gap-1 hx:relative hx:before:absolute hx:before:inset-y-1 hx:before:w-px hx:before:bg-gray-200 hx:before:content-[""] hx:dark:before:bg-neutral-800 hx:ltr:pl-3 hx:ltr:before:left-0 hx:rtl:pr-3 hx:rtl:before:right-0 hx:ltr:ml-3 hx:rtl:mr-3'>
      {{- range . }}
        {{- with .Headings }}
          {{- range . -}}
            <li>
              <a
                href="#{{ anchorize .ID }}"
                class="hx:flex hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:transition-colors [word-break:break-word] hx:cursor-pointer [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] hx:contrast-more:border hx:gap-2 hx:before:opacity-25 hx:before:content-['#'] hx:text-gray-500 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:text-neutral-400 hx:dark:hover:bg-primary-100/5 hx:dark:hover:text-gray-50 hx:contrast-more:text-gray-900 hx:contrast-more:dark:text-gray-50 hx:contrast-more:border-transparent hx:contrast-more:hover:border-gray-900 hx:contrast-more:dark:hover:border-gray-50"
              >
                {{- .Title | safeHTML | plainify | htmlUnescape -}}
              </a>
            </li>
          {{ end -}}
        {{ end -}}
      {{ end -}}
    </ul>
  {{ end }}
{{- end -}}

{{- define "sidebar-footer" -}}
  {{- range site.Menus.sidebar -}}
    {{- $name := or (T .Identifier) .Name -}}
    {{ if eq .Params.type "separator" }}
      <li class="[word-break:break-word] hx:mt-5 hx:mb-2 hx:px-2 hx:py-1.5 hx:text-sm hx:font-semibold hx:text-gray-900 hx:first:mt-0 hx:dark:text-gray-100">
        <span class="hx:cursor-default">{{ $name }}</span>
      </li>
    {{ else }}
      {{- $link := .URL -}}
      {{- with .PageRef -}}
        {{- if hasPrefix . "/" -}}
          {{- $link = relLangURL (strings.TrimPrefix "/" .) -}}
        {{- end -}}
      {{- end -}}
      <li>{{ template "sidebar-item-link" dict "active" false "title" $name "link" $link }}</li>
    {{ end }}
  {{- end -}}
{{- end -}}

{{- define "sidebar-mobile" -}}
  {{- range site.Menus.mobile -}}
    {{- $name := or (T .Identifier) .Name -}}
    {{ if eq .Params.type "separator" }}
      <li class="[word-break:break-word] hx-mt-5 hx-mb-2 hx-px-2 hx-py-1.5 hx-text-sm hx-font-semibold hx-text-gray-900 first:hx-mt-0 dark:hx-text-gray-100">
        <span class="hx-cursor-default">{{ $name }}</span>
      </li>
    {{ else }}
      {{- $link := .URL -}}
      {{- with .PageRef -}}
        {{- if hasPrefix . "/" -}}
          {{- $link = relLangURL (strings.TrimPrefix "/" .) -}}
        {{- end -}}
      {{- end -}}
      <li>{{ template "sidebar-item-link" dict "active" false "title" $name "link" $link }}</li>
    {{ end }}
  {{- end -}}
{{- end -}}

{{- define "sidebar-item-link" -}}
  {{- $external := strings.HasPrefix .link "http" -}}
  {{- $open := .open | default true -}}
  <a
    class="hx:flex hx:items-center hx:justify-between hx:gap-2 hx:cursor-pointer hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
    {{- if .active }}
      hextra-sidebar-active-item hx:bg-primary-100 hx:font-semibold hx:text-primary-800 hx:contrast-more:border hx:contrast-more:border-primary-500 hx:dark:bg-primary-400/10 hx:dark:text-primary-600 hx:contrast-more:dark:border-primary-500
    {{- else }}
      hx:text-gray-500 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:contrast-more:border hx:contrast-more:border-transparent hx:contrast-more:text-gray-900 hx:contrast-more:hover:border-gray-900 hx:dark:text-neutral-400 hx:dark:hover:bg-primary-100/5 hx:dark:hover:text-gray-50 hx:contrast-more:dark:text-gray-50 hx:contrast-more:dark:hover:border-gray-50
    {{- end -}}"
    href="{{ .link }}"
    {{ if $external }}target="_blank" rel="noreferrer"{{ end }}
  >
    {{- .title -}}
    {{- with .context }}
      {{- if or .RegularPages .Sections }}
        <span class="hextra-sidebar-collapsible-button">
          {{- template "sidebar-collapsible-button" -}}
        </span>
      {{- end }}
    {{ end -}}
  </a>
{{- end -}}

{{- define "sidebar-collapsible-button" -}}
  <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" class="hx:h-[18px] hx:min-w-[18px] hx:rounded-xs hx:p-0.5 hx:hover:bg-gray-800/5 hx:dark:hover:bg-gray-100/5"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" class="hx:origin-center hx:transition-transform hx:rtl:-rotate-180"></path></svg>
{{- end -}}

修改内容为新增第 28、158-175 行;

hextra 代码块优化

这个具体看:hugo 仅当代码块超过 1 行时显示行号

hextra 支持搜索 keywords

首先,不要将网站语言调整为中文,否则搜索体验非常差,英文搜索不了,应该是中文搜索的 js 问题。

在 assets 目录下新建 json 目录,然后新建一个 search-data.json 文件,具体目录结构如下:

      • search-data.json
  • 内容如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    {{/* FlexSearch Index Data */}}
    {{- $indexType := site.Params.search.flexsearch.index | default "content" -}}
    
    {{- if not (in (slice "content" "summary" "heading" "title" "keywords" ) $indexType) -}}
      {{- errorf "unknown flexsearch index type: %s" $indexType -}}
    {{- end -}}
    
    {{- $pages := where .Site.Pages "Kind" "in" (slice "page" "section") -}}
    {{- $pages = where $pages "Params.excludeSearch" "!=" true -}}
    {{- $pages = where $pages "Content" "!=" "" -}}
    
    {{- $output := dict -}}
    
    {{- range $index, $page := $pages -}}
      {{- $pageTitle := $page.LinkTitle | default $page.File.BaseFileName -}}
      {{- $pageLink := $page.RelPermalink -}}
      {{- $data := partial "utils/fragments" (dict "context" $page "type" $indexType) -}}
      {{- $keywords := $page.Keywords -}}
      {{- $result := delimit $keywords " " -}}
      {{ $data = merge $data (dict "" $result) }}
      {{- $output = $output | merge (dict $pageLink (dict "title" $pageTitle "data" $data)) -}}
    {{- end -}}
    
    {{- $output | jsonify -}}

    第 18-20 行为我新增代码,其余均是主题文件源码。

    hextra 汉化

    在 i18n 目录下新建 en.yaml 文件,内容如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    backToTop: "返回顶部"
    changeLanguage: "切换语言"
    changeTheme: "切换主题"
    copyCode: "复制代码"
    copyright: "© 2025 Hextra Project."
    dark: "深色"
    editThisPage: "在 GitHub 上编辑此页 →"
    lastUpdated: "最后更新于"
    light: "浅色"
    noResultsFound: "无结果"
    onThisPage: "此页上"
    tags: "标签"
    poweredBy: "由 Hextra 驱动"
    readMore: "更多 →"
    searchPlaceholder: "搜索。.."
    previous: "上一页"
    next: "下一页"
    system: "跟随系统"

    内容来自官方的中文翻译,由于为了搜索,我们网站的语言设置的是英文;

    hugo 日期格式调整

    修改 hugo.yaml,新增:

    1
    2
    
    params:
      dateFormat: "2006-01-02 15:04:05"

    本站完整的 hugo.yaml 配置如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    
    baseURL: 'https://www.kpromise.top/'
    languageCode: 'zh-cn'
    title: '有一个程序员'
    
    module:
      imports:
        - path: github.com/imfing/hextra
    
    enableEmoji: true
    hasCJKLanguage: true
    # 不要启用,启用后搜索不准确
    #defaultContentLanguage = "zh-cn"
    enableRobotsTXT: true
    enableInlineShortcodes: true
    
    markup:
      highlight:
        anchorLineNos: true
        codeFences: true
        guessSyntax: true
        hl_inline: false
        lineNoStart: 1
        lineNos: true
        lineNumbersInTable: true
        noClasses: false
        # style: 'github'
        tabWidth: 2
      goldmark:
        renderer:
          unsafe: true
    
    params:
      page:
        width: wide #wide, full, normal
      navbar:
        width:  wide
        displayLogo: false
      footer:
        displayCopyright: false
        displayPoweredBy: true
      search:
        enable: true
        type: flexsearch
        flexsearch:
          index: heading # content | summary | heading | title
          tokenize: full # full | forward | reverse | strict
      displayUpdatedDate: true
      dateFormat: "2006-01-02 15:04:05"
      blog:
        list:
          displayTags: true
          sortBy: date
          sortOrder: desc
    
    menu:
      main:
        - name: 搜索
          params:
            type: search
          weight: 1020
        - name: About
          weight: 1010
          pageRef: /about/
        # - name: Note
        #   weight: 1015
        #   url: https://note.kpromise.top
      mobile:
        - name: Golang
          weight: 1
          pageRef: /docs/golang/
        - name: Linux
          weight: 2
          pageRef: /docs/linux/
        - name: Java
          weight: 3
          pageRef: /docs/java/
        - name: About
          weight: 4
          pageRef: /about/
    
    permalinks:
      page:
        golang: /docs/:sections/:contentbasename/
        java: /docs/:sections/:contentbasename/
        linux: /docs/:sections/:contentbasename/
        blog: /:slug/
      section:
        golang: /docs/:sections/
        java: /docs/:sections/
        linux: /docs/:sections/
    
    outputs:
      home: 
        - HTML
      page:
        - HTML
      section:
        - HTML
    最后更新于