Compare commits

...

42 Commits

Author SHA1 Message Date
Xin
9fc253dff5 chore: cache tree branch without leaf 2023-10-24 22:52:10 +01:00
Xin
0125822785 chore: move inline template to partial 2023-10-24 22:22:57 +01:00
Xin
1e1d1c8716 chore: add back mobile nav 2023-10-24 21:39:43 +01:00
Xin
1048ee47d7 fix: unescape html for link title 2023-10-24 20:41:50 +01:00
Xin
5842f893a3 chore: add toc in section tree json 2023-10-24 20:41:28 +01:00
Xin
78ce7c2f2e fix: link in sidebar extra component 2023-10-24 20:17:39 +01:00
Xin
0652772c15 refactor: pre-calculate section tree structure json 2023-10-24 00:17:48 +01:00
Xin
13e4eb3414 chore: add section-tree.html 2023-10-23 21:59:26 +01:00
Xin
529fcd8a62 chore: add metric command 2023-10-23 19:44:18 +01:00
Xin
21a13c49f9 chore: inline partial sidebar switches 2023-10-22 13:57:55 +01:00
Xin
98d0a3dc73 refactor: split out sidebar elements into partials 2023-10-22 11:17:26 +01:00
Xin
24f3178ea8 perf: cache scripts 2023-10-22 11:01:19 +01:00
Xin
230cc438b7 perf: cache sidebar footer 2023-10-21 23:22:45 +01:00
Xin
214cb7994f perf: cache sidebear-collapsible-button 2023-10-21 23:18:42 +01:00
Xin
792ad4b569 perf: cache partial "search.html" 2023-10-21 23:13:30 +01:00
Xin
97e6945c04 feat: add option to set default theme and hide toggle button (#146)
resolves #135 

Light / dark theme can be configured via:

```yaml
  theme:
    # light | dark | system
    default: system
    displayToggle: true
```
2023-10-21 22:18:04 +01:00
Xin
93cb788e52 feat(search): support different search index types (#145)
* add support for different search index types: `content | summary | heading | title`
* resolves #139
2023-10-21 21:00:39 +01:00
88b0f1b2ab fix: broken "edit this page" link in windows (#143) 2023-10-18 22:33:36 +01:00
a31b46f5e3 chore(i18n): add translations of Korean (#144)
* chore(i18n): add translations of Korean

* Update ko.yaml

---------

Co-authored-by: Xin <xin@imfing.com>
2023-10-18 22:32:28 +01:00
Sid
6641d36b98 docs: add using Hextra as Git submodule (#137)
* docs: update docs relating to getting started and Hugo modules

* docs: add docs to use Hextra as Git submodule, closes #107

* chore: use paragraph instead of callout, refactor docs

* Update getting-started.md

* Update getting-started.md

---------

Co-authored-by: Xin <xin@imfing.com>
2023-10-16 14:28:28 +01:00
e42d01898a fix: add missing translations (#142)
* fix: add missing translation in blog list

* fix: add translatable variables

* Update en.yaml

---------

Co-authored-by: Xin <xin@imfing.com>
2023-10-16 13:30:31 +01:00
Xin
6cd4c55613 fix: callout styling issue with markdown content (#141) 2023-10-13 00:09:57 +01:00
Xin
cb09b7ce1e fix: skip scroll event if no backToTop element (#138) 2023-10-12 23:25:34 +01:00
96c6ff073f chore(i18n): update zh-cn.yaml (#136) 2023-10-11 23:22:07 +01:00
Xin
28a20e1e7e chore: add code copy button icons in js (#133) 2023-10-07 20:01:19 +01:00
Xin
5f4c7423d0 chore: add feature request issue template
[skip ci]
2023-10-04 20:54:29 +01:00
Xin
2bc4ed19e3 chore: add issue bug report template
[skip ci]
2023-10-04 20:51:22 +01:00
Xin
8aa6439132 feat: support custom primary saturation (#131)
* feat: support custom primary saturation

* chore: run npm run build:css

* docs: update instruction for customizing primary color
2023-10-04 20:41:59 +01:00
b7558aca44 feat: support empty prev/next pagination (#130)
[skip ci]
2023-10-04 20:13:49 +01:00
Xin
55ff819dae fix: not-prose p tag inconsistent style (#126)
* fix: not-prose p tag should reset styles

* chore: compile CSS
2023-10-03 09:12:30 +01:00
Xin
924d8508d0 fix: footer enable flag logic issue (#125) 2023-10-03 08:51:41 +01:00
1b932f260a chore: add Vietnamese translation (#123)
[skip ci]
2023-10-03 08:36:35 +01:00
5768ed4695 chore: add Portuguese translation (#119) 2023-10-02 23:53:53 +01:00
f4cea168b1 chore: spanish translations es.yaml (#114)
[skip ci]
2023-10-02 11:57:12 +01:00
e2d00fdcd0 chore: add Swahili translation (#113)
[skip ci]
2023-10-01 23:13:18 +01:00
Xin
103faa24f3 chore: re-compile CSS 2023-10-01 09:25:02 +00:00
d1bed05843 feat: Back To Top (#105)
* Scroll to top

* Update scripts.html

---------

Co-authored-by: Xin <xin@imfing.com>
2023-10-01 10:06:28 +01:00
Xin
2df3c563bf fix: use div for copyright container in footer (#104) 2023-09-27 20:40:35 +01:00
Xin
ec02eb34fe fix: search shortcut based on user platform (#101)
* fix: search shortcut based on user platform

* chore: make it work with iPad and iPhone as well
2023-09-27 19:47:20 +01:00
Xin
46dea718e6 docs: add giscus comments system (#96)
* chore: rename comment to comments

* docs: add giscus comments

* docs: update
2023-09-26 23:33:27 +01:00
Xin
adf5a113fc fix: giscus theme and language display issues (#95)
* chore: minor update giscus template

* fix: theme toggle should select all

* chore: example configs for giscus

* fix: language code in giscus
2023-09-26 22:12:38 +01:00
6a19ac31c0 feat: add giscus support (#92)
* feat: add giscus support #89

* Update comment.html

* Update giscus.html

* Update giscus.html

---------

Co-authored-by: Xin <xin@imfing.com>
2023-09-26 20:47:22 +01:00
54 changed files with 993 additions and 345 deletions

40
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,40 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Description**
<!-- Provide a clear and concise description of the bug -->
**Steps To Reproduce**
1.
2.
3.
**Expected Behavior**
<!-- What should have happened? -->
**Actual Behavior**
<!-- What happened instead? -->
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem -->
**Environment**
- Hugo Version: [e.g., 0.85.0]
- Browser/OS: [e.g., Chrome, MacOS]
- Theme Version: [e.g., v2.0]
**Additional Context**
<!-- Add any other context about the problem here -->

View File

@ -0,0 +1,24 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Feature Description**
<!-- Provide a clear and concise description of the feature -->
**Problem/Solution**
<!-- What problem will this feature solve? Or what new capability will it add? -->
**Alternatives Considered**
<!-- Have you considered any alternative solutions or workarounds? -->
**Additional Context**
<!-- Add any other context or screenshots about the feature request here -->

View File

@ -697,6 +697,12 @@ video {
.h-2 { .h-2 {
height: 0.5rem; height: 0.5rem;
} }
.h-3 {
height: 0.75rem;
}
.h-3\.5 {
height: 0.875rem;
}
.h-4 { .h-4 {
height: 1rem; height: 1rem;
} }
@ -941,6 +947,10 @@ video {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity)); border-color: rgb(229 231 235 / var(--tw-border-opacity));
} }
.border-gray-500 {
--tw-border-opacity: 1;
border-color: rgb(107 114 128 / var(--tw-border-opacity));
}
.border-orange-100 { .border-orange-100 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(255 237 213 / var(--tw-border-opacity)); border-color: rgb(255 237 213 / var(--tw-border-opacity));
@ -977,18 +987,18 @@ video {
} }
.bg-primary-100 { .bg-primary-100 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: hsl(var(--primary-hue) 100% 94% / var(--tw-bg-opacity)); background-color: hsl(var(--primary-hue) var(--primary-saturation) 94% / var(--tw-bg-opacity));
} }
.bg-primary-400 { .bg-primary-400 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: hsl(var(--primary-hue) 100% 66% / var(--tw-bg-opacity)); background-color: hsl(var(--primary-hue) var(--primary-saturation) 66% / var(--tw-bg-opacity));
} }
.bg-primary-600 { .bg-primary-600 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: hsl(var(--primary-hue) 100% 45% / var(--tw-bg-opacity)); background-color: hsl(var(--primary-hue) var(--primary-saturation) 45% / var(--tw-bg-opacity));
} }
.bg-primary-700\/5 { .bg-primary-700\/5 {
background-color: hsl(var(--primary-hue) 100% 39% / 0.05); background-color: hsl(var(--primary-hue) var(--primary-saturation) 39% / 0.05);
} }
.bg-red-100 { .bg-red-100 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
@ -1251,7 +1261,7 @@ video {
} }
.text-primary-800 { .text-primary-800 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: hsl(var(--primary-hue) 100% 32% / var(--tw-text-opacity)); color: hsl(var(--primary-hue) var(--primary-saturation) 32% / var(--tw-text-opacity));
} }
.text-red-900 { .text-red-900 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
@ -1370,6 +1380,9 @@ video {
.duration-200 { .duration-200 {
transition-duration: 200ms; transition-duration: 200ms;
} }
.duration-75 {
transition-duration: 75ms;
}
.ease-in { .ease-in {
transition-timing-function: cubic-bezier(0.4, 0, 1, 1); transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
} }
@ -1426,7 +1439,7 @@ video {
} }
} }
:is(html[class~="dark"] .content h2) { :is(html[class~="dark"] .content h2) {
border-color: hsl(var(--primary-hue) 100% 94% / 0.1); border-color: hsl(var(--primary-hue) var(--primary-saturation) 94% / 0.1);
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(241 245 249 / var(--tw-text-opacity)); color: rgb(241 245 249 / var(--tw-text-opacity));
} }
@ -1492,9 +1505,13 @@ video {
.content p:first-child { .content p:first-child {
margin-top: 0px; margin-top: 0px;
} }
.content .not-prose p {
margin-top: 0px;
line-height: 1.5;
}
.content a { .content a {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: hsl(var(--primary-hue) 100% 45% / var(--tw-text-opacity)); color: hsl(var(--primary-hue) var(--primary-saturation) 45% / var(--tw-text-opacity));
text-decoration-line: underline; text-decoration-line: underline;
text-decoration-thickness: from-font; text-decoration-thickness: from-font;
text-underline-position: from-font; text-underline-position: from-font;
@ -1532,7 +1549,7 @@ video {
margin-bottom: 1rem; margin-bottom: 1rem;
overflow-x: auto; overflow-x: auto;
border-radius: 0.75rem; border-radius: 0.75rem;
background-color: hsl(var(--primary-hue) 100% 39% / 0.05); background-color: hsl(var(--primary-hue) var(--primary-saturation) 39% / 0.05);
padding-top: 1rem; padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
font-size: .9em; font-size: .9em;
@ -1544,18 +1561,18 @@ video {
.content pre:not(.code-block pre) { .content pre:not(.code-block pre) {
border-width: 1px; border-width: 1px;
border-color: hsl(var(--primary-hue) 100% 24% / 0.2); border-color: hsl(var(--primary-hue) var(--primary-saturation) 24% / 0.2);
--tw-contrast: contrast(1.5); --tw-contrast: contrast(1.5);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
} }
} }
:is(html[class~="dark"] .content pre:not(.code-block pre)) { :is(html[class~="dark"] .content pre:not(.code-block pre)) {
background-color: hsl(var(--primary-hue) 100% 77% / 0.1); background-color: hsl(var(--primary-hue) var(--primary-saturation) 77% / 0.1);
} }
@media (prefers-contrast: more) { @media (prefers-contrast: more) {
:is(html[class~="dark"] .content pre:not(.code-block pre)) { :is(html[class~="dark"] .content pre:not(.code-block pre)) {
border-color: hsl(var(--primary-hue) 100% 94% / 0.4); border-color: hsl(var(--primary-hue) var(--primary-saturation) 94% / 0.4);
} }
} }
.content code:not(.code-block code) { .content code:not(.code-block code) {
@ -2151,7 +2168,7 @@ article details > summary::before {
} }
.code-block pre { .code-block pre {
overflow-x: auto; overflow-x: auto;
background-color: hsl(var(--primary-hue) 100% 39% / 0.05); background-color: hsl(var(--primary-hue) var(--primary-saturation) 39% / 0.05);
font-size: .9em; font-size: .9em;
font-weight: 500; font-weight: 500;
-webkit-font-smoothing: auto; -webkit-font-smoothing: auto;
@ -2161,18 +2178,18 @@ article details > summary::before {
.code-block pre { .code-block pre {
border-width: 1px; border-width: 1px;
border-color: hsl(var(--primary-hue) 100% 24% / 0.2); border-color: hsl(var(--primary-hue) var(--primary-saturation) 24% / 0.2);
--tw-contrast: contrast(1.5); --tw-contrast: contrast(1.5);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
} }
} }
:is(html[class~="dark"] .code-block pre) { :is(html[class~="dark"] .code-block pre) {
background-color: hsl(var(--primary-hue) 100% 77% / 0.1); background-color: hsl(var(--primary-hue) var(--primary-saturation) 77% / 0.1);
} }
@media (prefers-contrast: more) { @media (prefers-contrast: more) {
:is(html[class~="dark"] .code-block pre) { :is(html[class~="dark"] .code-block pre) {
border-color: hsl(var(--primary-hue) 100% 94% / 0.4); border-color: hsl(var(--primary-hue) var(--primary-saturation) 94% / 0.4);
} }
} }
.code-block .filename { .code-block .filename {
@ -2185,7 +2202,7 @@ article details > summary::before {
white-space: nowrap; white-space: nowrap;
border-top-left-radius: 0.75rem; border-top-left-radius: 0.75rem;
border-top-right-radius: 0.75rem; border-top-right-radius: 0.75rem;
background-color: hsl(var(--primary-hue) 100% 39% / 0.05); background-color: hsl(var(--primary-hue) var(--primary-saturation) 39% / 0.05);
padding-top: 0.5rem; padding-top: 0.5rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
padding-left: 1rem; padding-left: 1rem;
@ -2195,7 +2212,7 @@ article details > summary::before {
color: rgb(55 65 81 / var(--tw-text-opacity)); color: rgb(55 65 81 / var(--tw-text-opacity));
} }
:is(html[class~="dark"] .code-block .filename) { :is(html[class~="dark"] .code-block .filename) {
background-color: hsl(var(--primary-hue) 100% 77% / 0.1); background-color: hsl(var(--primary-hue) var(--primary-saturation) 77% / 0.1);
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(229 231 235 / var(--tw-text-opacity)); color: rgb(229 231 235 / var(--tw-text-opacity));
} }
@ -2248,7 +2265,7 @@ article details > summary::before {
.chroma .hl { .chroma .hl {
display: block; display: block;
width: 100%; width: 100%;
background-color: hsl(var(--primary-hue) 100% 32% / 0.1); background-color: hsl(var(--primary-hue) var(--primary-saturation) 32% / 0.1);
} }
.hextra-cards { .hextra-cards {
grid-template-columns: repeat(auto-fill, minmax(max(250px, calc((100% - 1rem * 2) / var(--rows))), 1fr)); grid-template-columns: repeat(auto-fill, minmax(max(250px, calc((100% - 1rem * 2) / var(--rows))), 1fr));
@ -2340,13 +2357,13 @@ article details > summary::before {
} }
.search-wrapper li .active { .search-wrapper li .active {
border-radius: 0.375rem; border-radius: 0.375rem;
background-color: hsl(var(--primary-hue) 100% 50% / 0.1); background-color: hsl(var(--primary-hue) var(--primary-saturation) 50% / 0.1);
} }
@media (prefers-contrast: more) { @media (prefers-contrast: more) {
.search-wrapper li .active { .search-wrapper li .active {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: hsl(var(--primary-hue) 100% 50% / var(--tw-border-opacity)); border-color: hsl(var(--primary-hue) var(--primary-saturation) 50% / var(--tw-border-opacity));
} }
} }
.search-wrapper .no-result { .search-wrapper .no-result {
@ -2433,7 +2450,7 @@ article details > summary::before {
} }
.search-wrapper .match { .search-wrapper .match {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: hsl(var(--primary-hue) 100% 45% / var(--tw-text-opacity)); color: hsl(var(--primary-hue) var(--primary-saturation) 45% / var(--tw-text-opacity));
} }
@media (max-width: 767px) { @media (max-width: 767px) {
.sidebar-container { .sidebar-container {
@ -2572,11 +2589,13 @@ body {
} }
:root { :root {
--primary-hue: 212deg; --primary-hue: 212deg;
--primary-saturation: 100%;
--navbar-height: 4rem; --navbar-height: 4rem;
--menu-height: 3.75rem; --menu-height: 3.75rem;
} }
.dark { .dark {
--primary-hue: 204deg; --primary-hue: 204deg;
--primary-saturation: 100%;
} }
.placeholder\:text-gray-500::-moz-placeholder { .placeholder\:text-gray-500::-moz-placeholder {
--tw-text-opacity: 1; --tw-text-opacity: 1;
@ -2660,6 +2679,10 @@ body {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(156 163 175 / var(--tw-border-opacity)); border-color: rgb(156 163 175 / var(--tw-border-opacity));
} }
.hover\:border-gray-900:hover {
--tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity));
}
.hover\:bg-gray-100:hover { .hover\:bg-gray-100:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity)); background-color: rgb(243 244 246 / var(--tw-bg-opacity));
@ -2669,11 +2692,11 @@ body {
} }
.hover\:bg-primary-50:hover { .hover\:bg-primary-50:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: hsl(var(--primary-hue) 100% 97% / var(--tw-bg-opacity)); background-color: hsl(var(--primary-hue) var(--primary-saturation) 97% / var(--tw-bg-opacity));
} }
.hover\:bg-primary-700:hover { .hover\:bg-primary-700:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: hsl(var(--primary-hue) 100% 39% / var(--tw-bg-opacity)); background-color: hsl(var(--primary-hue) var(--primary-saturation) 39% / var(--tw-bg-opacity));
} }
.hover\:bg-slate-50:hover { .hover\:bg-slate-50:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
@ -2693,7 +2716,7 @@ body {
} }
.hover\:text-primary-600:hover { .hover\:text-primary-600:hover {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: hsl(var(--primary-hue) 100% 45% / var(--tw-text-opacity)); color: hsl(var(--primary-hue) var(--primary-saturation) 45% / var(--tw-text-opacity));
} }
.hover\:opacity-60:hover { .hover\:opacity-60:hover {
opacity: 0.6; opacity: 0.6;
@ -2730,7 +2753,7 @@ body {
} }
.focus\:ring-primary-300:focus { .focus\:ring-primary-300:focus {
--tw-ring-opacity: 1; --tw-ring-opacity: 1;
--tw-ring-color: hsl(var(--primary-hue) 100% 77% / var(--tw-ring-opacity)); --tw-ring-color: hsl(var(--primary-hue) var(--primary-saturation) 77% / var(--tw-ring-opacity));
} }
.active\:bg-gray-400\/20:active { .active\:bg-gray-400\/20:active {
background-color: rgb(156 163 175 / 0.2); background-color: rgb(156 163 175 / 0.2);
@ -2772,11 +2795,11 @@ body {
} }
.data-\[state\=selected\]\:border-primary-500[data-state=selected] { .data-\[state\=selected\]\:border-primary-500[data-state=selected] {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: hsl(var(--primary-hue) 100% 50% / var(--tw-border-opacity)); border-color: hsl(var(--primary-hue) var(--primary-saturation) 50% / var(--tw-border-opacity));
} }
.data-\[state\=selected\]\:text-primary-600[data-state=selected] { .data-\[state\=selected\]\:text-primary-600[data-state=selected] {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: hsl(var(--primary-hue) 100% 45% / var(--tw-text-opacity)); color: hsl(var(--primary-hue) var(--primary-saturation) 45% / var(--tw-text-opacity));
} }
.group[data-theme=dark] .group-data-\[theme\=dark\]\:hidden { .group[data-theme=dark] .group-data-\[theme\=dark\]\:hidden {
display: none; display: none;
@ -2918,6 +2941,11 @@ body {
border-color: currentColor; border-color: currentColor;
} }
.contrast-more\:border-gray-800 {
--tw-border-opacity: 1;
border-color: rgb(31 41 55 / var(--tw-border-opacity));
}
.contrast-more\:border-gray-900 { .contrast-more\:border-gray-900 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity)); border-color: rgb(17 24 39 / var(--tw-border-opacity));
@ -2930,7 +2958,7 @@ body {
.contrast-more\:border-primary-500 { .contrast-more\:border-primary-500 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: hsl(var(--primary-hue) 100% 50% / var(--tw-border-opacity)); border-color: hsl(var(--primary-hue) var(--primary-saturation) 50% / var(--tw-border-opacity));
} }
.contrast-more\:border-transparent { .contrast-more\:border-transparent {
@ -2993,6 +3021,10 @@ body {
:is(html[class~="dark"] .dark\:border-gray-100\/20) { :is(html[class~="dark"] .dark\:border-gray-100\/20) {
border-color: rgb(243 244 246 / 0.2); border-color: rgb(243 244 246 / 0.2);
} }
:is(html[class~="dark"] .dark\:border-gray-400) {
--tw-border-opacity: 1;
border-color: rgb(156 163 175 / var(--tw-border-opacity));
}
:is(html[class~="dark"] .dark\:border-neutral-700) { :is(html[class~="dark"] .dark\:border-neutral-700) {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(64 64 64 / var(--tw-border-opacity)); border-color: rgb(64 64 64 / var(--tw-border-opacity));
@ -3038,14 +3070,14 @@ body {
background-color: rgb(251 146 60 / 0.2); background-color: rgb(251 146 60 / 0.2);
} }
:is(html[class~="dark"] .dark\:bg-primary-300\/10) { :is(html[class~="dark"] .dark\:bg-primary-300\/10) {
background-color: hsl(var(--primary-hue) 100% 77% / 0.1); background-color: hsl(var(--primary-hue) var(--primary-saturation) 77% / 0.1);
} }
:is(html[class~="dark"] .dark\:bg-primary-400\/10) { :is(html[class~="dark"] .dark\:bg-primary-400\/10) {
background-color: hsl(var(--primary-hue) 100% 66% / 0.1); background-color: hsl(var(--primary-hue) var(--primary-saturation) 66% / 0.1);
} }
:is(html[class~="dark"] .dark\:bg-primary-600) { :is(html[class~="dark"] .dark\:bg-primary-600) {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: hsl(var(--primary-hue) 100% 45% / var(--tw-bg-opacity)); background-color: hsl(var(--primary-hue) var(--primary-saturation) 45% / var(--tw-bg-opacity));
} }
:is(html[class~="dark"] .dark\:bg-red-900\/30) { :is(html[class~="dark"] .dark\:bg-red-900\/30) {
background-color: rgb(127 29 29 / 0.3); background-color: rgb(127 29 29 / 0.3);
@ -3099,7 +3131,7 @@ body {
} }
:is(html[class~="dark"] .dark\:text-primary-600) { :is(html[class~="dark"] .dark\:text-primary-600) {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: hsl(var(--primary-hue) 100% 45% / var(--tw-text-opacity)); color: hsl(var(--primary-hue) var(--primary-saturation) 45% / var(--tw-text-opacity));
} }
:is(html[class~="dark"] .dark\:text-red-200) { :is(html[class~="dark"] .dark\:text-red-200) {
--tw-text-opacity: 1; --tw-text-opacity: 1;
@ -3152,6 +3184,10 @@ body {
--tw-invert: invert(100%); --tw-invert: invert(100%);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
} }
:is(html[class~="dark"] .dark\:hover\:border-gray-100:hover) {
--tw-border-opacity: 1;
border-color: rgb(243 244 246 / var(--tw-border-opacity));
}
:is(html[class~="dark"] .dark\:hover\:border-gray-600:hover) { :is(html[class~="dark"] .dark\:hover\:border-gray-600:hover) {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity)); border-color: rgb(75 85 99 / var(--tw-border-opacity));
@ -3184,14 +3220,14 @@ body {
background-color: rgb(23 23 23 / var(--tw-bg-opacity)); background-color: rgb(23 23 23 / var(--tw-bg-opacity));
} }
:is(html[class~="dark"] .dark\:hover\:bg-primary-100\/5:hover) { :is(html[class~="dark"] .dark\:hover\:bg-primary-100\/5:hover) {
background-color: hsl(var(--primary-hue) 100% 94% / 0.05); background-color: hsl(var(--primary-hue) var(--primary-saturation) 94% / 0.05);
} }
:is(html[class~="dark"] .dark\:hover\:bg-primary-700:hover) { :is(html[class~="dark"] .dark\:hover\:bg-primary-700:hover) {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: hsl(var(--primary-hue) 100% 39% / var(--tw-bg-opacity)); background-color: hsl(var(--primary-hue) var(--primary-saturation) 39% / var(--tw-bg-opacity));
} }
:is(html[class~="dark"] .hover\:dark\:bg-primary-500\/10):hover { :is(html[class~="dark"] .hover\:dark\:bg-primary-500\/10):hover {
background-color: hsl(var(--primary-hue) 100% 50% / 0.1); background-color: hsl(var(--primary-hue) var(--primary-saturation) 50% / 0.1);
} }
:is(html[class~="dark"] .dark\:hover\:text-gray-100:hover) { :is(html[class~="dark"] .dark\:hover\:text-gray-100:hover) {
--tw-text-opacity: 1; --tw-text-opacity: 1;
@ -3219,7 +3255,7 @@ body {
} }
:is(html[class~="dark"] .hover\:dark\:text-primary-600):hover { :is(html[class~="dark"] .hover\:dark\:text-primary-600):hover {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: hsl(var(--primary-hue) 100% 45% / var(--tw-text-opacity)); color: hsl(var(--primary-hue) var(--primary-saturation) 45% / var(--tw-text-opacity));
} }
:is(html[class~="dark"] .dark\:hover\:shadow-none:hover) { :is(html[class~="dark"] .dark\:hover\:shadow-none:hover) {
--tw-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000;
@ -3232,7 +3268,7 @@ body {
} }
:is(html[class~="dark"] .dark\:focus\:ring-primary-800:focus) { :is(html[class~="dark"] .dark\:focus\:ring-primary-800:focus) {
--tw-ring-opacity: 1; --tw-ring-opacity: 1;
--tw-ring-color: hsl(var(--primary-hue) 100% 32% / var(--tw-ring-opacity)); --tw-ring-color: hsl(var(--primary-hue) var(--primary-saturation) 32% / var(--tw-ring-opacity));
} }
@media (prefers-contrast: more) { @media (prefers-contrast: more) {
@ -3252,7 +3288,7 @@ body {
:is(html[class~="dark"] .contrast-more\:dark\:border-primary-500) { :is(html[class~="dark"] .contrast-more\:dark\:border-primary-500) {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: hsl(var(--primary-hue) 100% 50% / var(--tw-border-opacity)); border-color: hsl(var(--primary-hue) var(--primary-saturation) 50% / var(--tw-border-opacity));
} }
:is(html[class~="dark"] .dark\:contrast-more\:border-neutral-400) { :is(html[class~="dark"] .dark\:contrast-more\:border-neutral-400) {

View File

@ -21,10 +21,12 @@ body {
:root { :root {
--primary-hue: 212deg; --primary-hue: 212deg;
--primary-saturation: 100%;
--navbar-height: 4rem; --navbar-height: 4rem;
--menu-height: 3.75rem; --menu-height: 3.75rem;
} }
.dark { .dark {
--primary-hue: 204deg; --primary-hue: 204deg;
--primary-saturation: 100%;
} }

View File

@ -20,6 +20,9 @@
p { p {
@apply mt-6 leading-7 first:mt-0; @apply mt-6 leading-7 first:mt-0;
} }
.not-prose p {
@apply mt-0 leading-normal;
}
a { a {
@apply text-primary-600 underline decoration-from-font [text-underline-position:from-font]; @apply text-primary-600 underline decoration-from-font [text-underline-position:from-font];
} }

22
assets/js/back-to-top.js Normal file
View File

@ -0,0 +1,22 @@
// Back to top button
document.addEventListener("DOMContentLoaded", function () {
const backToTop = document.querySelector("#backToTop");
if (backToTop) {
document.addEventListener("scroll", (e) => {
if (window.scrollY > 300) {
backToTop.classList.remove("opacity-0");
} else {
backToTop.classList.add("opacity-0");
}
});
}
});
function scrollUp() {
window.scroll({
top: 0,
left: 0,
behavior: "smooth",
});
}

View File

@ -1,30 +1,63 @@
document.querySelectorAll('.code-copy-btn').forEach(function (button) { // Copy button for code blocks
button.addEventListener('click', function (e) {
e.preventDefault(); document.addEventListener('DOMContentLoaded', function () {
const targetId = button.getAttribute('data-clipboard-target'); const getCopyIcon = () => {
const target = document.querySelector(targetId); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
let codeElement; svg.innerHTML = `
if (target.tagName === 'CODE') { <path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
codeElement = target; `;
} else { svg.setAttribute('fill', 'none');
// Select the last code element in case line numbers are present svg.setAttribute('viewBox', '0 0 24 24');
const codeElements = target.querySelectorAll('code'); svg.setAttribute('stroke', 'currentColor');
codeElement = codeElements[codeElements.length - 1]; svg.setAttribute('stroke-width', '2');
} return svg;
if (codeElement) { }
// Replace double newlines with single newlines in the innerText
// as each line inside <span> has trailing newline '\n' const getSuccessIcon = () => {
const code = codeElement.innerText.replace(/\n\n/g, '\n'); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
navigator.clipboard.writeText(code).then(function () { svg.innerHTML = `
button.classList.add('copied'); <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
setTimeout(function () { `;
button.classList.remove('copied'); svg.setAttribute('fill', 'none');
}, 500); svg.setAttribute('viewBox', '0 0 24 24');
}).catch(function (err) { svg.setAttribute('stroke', 'currentColor');
console.error('Failed to copy text: ', err); svg.setAttribute('stroke-width', '2');
}); return svg;
} else { }
console.error('Target element not found');
} document.querySelectorAll('.code-copy-btn').forEach(function (button) {
// Add copy and success icons
button.querySelector('.copy-icon')?.appendChild(getCopyIcon());
button.querySelector('.success-icon')?.appendChild(getSuccessIcon());
// Add click event listener for copy button
button.addEventListener('click', function (e) {
e.preventDefault();
const targetId = button.getAttribute('data-clipboard-target');
const target = document.querySelector(targetId);
let codeElement;
if (target.tagName === 'CODE') {
codeElement = target;
} else {
// Select the last code element in case line numbers are present
const codeElements = target.querySelectorAll('code');
codeElement = codeElements[codeElements.length - 1];
}
if (codeElement) {
// Replace double newlines with single newlines in the innerText
// as each line inside <span> has trailing newline '\n'
const code = codeElement.innerText.replace(/\n\n/g, '\n');
navigator.clipboard.writeText(code).then(function () {
button.classList.add('copied');
setTimeout(function () {
button.classList.remove('copied');
}, 500);
}).catch(function (err) {
console.error('Failed to copy text: ', err);
});
} else {
console.error('Target element not found');
}
});
}); });
}); });

View File

@ -1,5 +1,16 @@
// Search functionality using FlexSearch. // Search functionality using FlexSearch.
// Change shortcut key to cmd+k on Mac, iPad or iPhone.
document.addEventListener("DOMContentLoaded", function () {
if (/iPad|iPhone|Macintosh/.test(navigator.userAgent)) {
// select the kbd element under the .search-wrapper class
const keys = document.querySelectorAll(".search-wrapper kbd");
keys.forEach(key => {
key.innerHTML = '<span class="text-xs">⌘</span>K';
});
}
});
// Render the search data as JSON. // Render the search data as JSON.
// {{ $searchDataFile := printf "%s.search-data.json" .Language.Lang }} // {{ $searchDataFile := printf "%s.search-data.json" .Language.Lang }}
// {{ $searchData := resources.Get "json/search-data.json" | resources.ExecuteAsTemplate $searchDataFile . }} // {{ $searchData := resources.Get "json/search-data.json" | resources.ExecuteAsTemplate $searchDataFile . }}

View File

@ -1,40 +1,51 @@
// Dark theme toggle // Light / Dark theme toggle
(function () {
const defaultTheme = '{{ site.Params.theme.default | default `system`}}'
const themeToggleButtons = document.querySelectorAll(".theme-toggle"); const themeToggleButtons = document.querySelectorAll(".theme-toggle");
// Change the icons inside the button based on previous settings // Change the icons of the buttons based on previous settings or system theme
if ( if (
localStorage.getItem("color-theme") === "dark" || localStorage.getItem("color-theme") === "dark" ||
(!("color-theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches) (!("color-theme" in localStorage) &&
) { ((window.matchMedia("(prefers-color-scheme: dark)").matches && defaultTheme === "system") || defaultTheme === "dark"))
themeToggleButtons.forEach((el) => el.dataset.theme = "dark"); ) {
} else { themeToggleButtons.forEach((el) => el.dataset.theme = "dark");
themeToggleButtons.forEach((el) => el.dataset.theme = "light"); } else {
} themeToggleButtons.forEach((el) => el.dataset.theme = "light");
}
themeToggleButtons.forEach((el) => { // Add click event handler to the buttons
el.addEventListener("click", function () { themeToggleButtons.forEach((el) => {
if (localStorage.getItem("color-theme")) { el.addEventListener("click", function () {
if (localStorage.getItem("color-theme") === "light") { if (localStorage.getItem("color-theme")) {
document.documentElement.classList.add("dark"); if (localStorage.getItem("color-theme") === "light") {
document.documentElement.style.colorScheme = "dark"; setDarkTheme();
localStorage.setItem("color-theme", "dark"); localStorage.setItem("color-theme", "dark");
} else {
setLightTheme();
localStorage.setItem("color-theme", "light");
}
} else { } else {
document.documentElement.classList.remove("dark"); if (document.documentElement.classList.contains("dark")) {
document.documentElement.style.colorScheme = "light"; setLightTheme();
localStorage.setItem("color-theme", "light"); localStorage.setItem("color-theme", "light");
} else {
setDarkTheme();
localStorage.setItem("color-theme", "dark");
}
} }
} else { el.dataset.theme = document.documentElement.classList.contains("dark") ? "dark" : "light";
if (document.documentElement.classList.contains("dark")) { });
document.documentElement.classList.remove("dark");
document.documentElement.style.colorScheme = "light";
localStorage.setItem("color-theme", "light");
} else {
document.documentElement.classList.add("dark");
document.documentElement.style.colorScheme = "dark";
localStorage.setItem("color-theme", "dark");
}
}
el.dataset.theme = document.documentElement.classList.contains("dark") ? "dark" : "light";
}); });
});
// Listen for system theme changes
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
if (defaultTheme === "system" && !("color-theme" in localStorage)) {
e.matches ? setDarkTheme() : setLightTheme();
themeToggleButtons.forEach((el) =>
el.dataset.theme = document.documentElement.classList.contains("dark") ? "dark" : "light"
);
}
});
})();

View File

@ -1,3 +1,10 @@
{{/* FlexSearch Index Data */}}
{{- $indexType := site.Params.search.flexsearch.index | default "content" -}}
{{- if not (in (slice "content" "summary" "heading" "title" ) $indexType) -}}
{{- errorf "unknown flexsearch index type: %s" $indexType -}}
{{- end -}}
{{- $pages := where .Site.Pages "Kind" "in" (slice "page" "section") -}} {{- $pages := where .Site.Pages "Kind" "in" (slice "page" "section") -}}
{{- $pages = where $pages "Params.excludeSearch" "!=" true -}} {{- $pages = where $pages "Params.excludeSearch" "!=" true -}}
{{- $pages = where $pages "Content" "!=" "" -}} {{- $pages = where $pages "Content" "!=" "" -}}
@ -7,7 +14,7 @@
{{- range $index, $page := $pages -}} {{- range $index, $page := $pages -}}
{{- $pageTitle := $page.LinkTitle | default $page.File.BaseFileName -}} {{- $pageTitle := $page.LinkTitle | default $page.File.BaseFileName -}}
{{- $pageLink := $page.RelPermalink -}} {{- $pageLink := $page.RelPermalink -}}
{{- $data := partial "utils/fragments" $page -}} {{- $data := partial "utils/fragments" (dict "context" $page "type" $indexType) -}}
{{- $output = $output | merge (dict $pageLink (dict "title" $pageTitle "data" $data)) -}} {{- $output = $output | merge (dict $pageLink (dict "title" $pageTitle "data" $data)) -}}
{{- end -}} {{- end -}}

View File

@ -12,4 +12,5 @@ This section covers some advanced topics of the theme.
{{< cards >}} {{< cards >}}
{{< card link="multi-language" title="Multi-language" icon="translate" >}} {{< card link="multi-language" title="Multi-language" icon="translate" >}}
{{< card link="customization" title="Customization" icon="pencil" >}} {{< card link="customization" title="Customization" icon="pencil" >}}
{{< card link="comments" title="Comments System" icon="chat-alt" >}}
{{< /cards >}} {{< /cards >}}

View File

@ -0,0 +1,39 @@
---
title: Comments System
linkTitle: Comments
---
Hextra supports adding comments system to your site.
Currently [giscus](https://giscus.app/) is supported.
<!--more-->
## giscus
[giscus](https://giscus.app/) is a comments system powered by [GitHub Discussions](https://docs.github.com/en/discussions). It is free and open source.
To enable giscus, you need to add the following to the site configuration file:
```yaml {filename="hugo.yaml"}
params:
comments:
enable: false
type: giscus
giscus:
repo: <repository>
repoId: <repository ID>
category: <category>
categoryId: <category ID>
```
The giscus configurations can be constructed from the [giscus.app](https://giscus.app/) website. More details can also be found there.
Comments can be enabled or disabled for a specific page in the page front matter:
```yaml {filename="content/docs/about.md"}
---
title: About
comments: true
---
```

View File

@ -34,11 +34,12 @@ The color of text mixed with `other text` can customized with:
### Primary Color ### Primary Color
The primary color of the theme can be customized by setting the `--primary-hue` variable: The primary color of the theme can be customized by setting the `--primary-hue` and `--primary-saturation` variables:
```css {filename="assets/css/custom.css"} ```css {filename="assets/css/custom.css"}
:root { :root {
--primary-hue: 100deg; --primary-hue: 100deg;
--primary-saturation: 90%;
} }
``` ```

View File

@ -19,18 +19,29 @@ We have provided a [GitHub Actions workflow](https://docs.github.com/en/pages/ge
## Start as New Project ## Start as New Project
### Prerequisites There are two main ways to add the Hextra theme to your Hugo project.
Before we start, make sure we have [Hugo](https://gohugo.io/) installed. 1. **Hugo Modules (Recommended)**: The simplest and recommended method. [Hugo modules](https://gohugo.io/hugo-modules/) let you pull in the theme directly from its online source. Theme is downloaded automatically and managed by Hugo.
Please refer to Hugo's [official installation guide](https://gohugo.io/installation/) for more details.
[Hugo modules](https://gohugo.io/hugo-modules/) are the recommended way to manage Hugo themes. To use Hugo modules, we need to install [Git](https://git-scm.com/) and [Go](https://go.dev/). 2. **Git Submodule**: Alternatively, add Hextra as a [Git Submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules). The theme will be downloaded by Git and stored in your project's `themes` folder.
### Setup Hextra as Hugo module
#### Prerequisites
Before starting, you need to have the following softwares installed:
- [Hugo (extended version)](https://gohugo.io/installation/)
- [Git](https://git-scm.com/)
- [Go](https://go.dev/)
#### Steps
{{% steps %}} {{% steps %}}
### Initialize a new Hugo site ### Initialize a new Hugo site
```bash ```shell
$ hugo new site my-site --format=yaml $ hugo new site my-site --format=yaml
``` ```
@ -45,7 +56,7 @@ $ hugo mod init github.com/username/my-site
$ hugo mod get github.com/imfing/hextra $ hugo mod get github.com/imfing/hextra
``` ```
Edit `hugo.yaml` to enable Hextra theme: Configure `hugo.yaml` to use Hextra theme by adding the following:
```yaml ```yaml
module: module:
@ -55,7 +66,7 @@ module:
### Create your first content pages ### Create your first content pages
Let's create a new content page for the home page and the documentation page: Let's create new content page for the home page and the documentation page:
```shell ```shell
$ hugo new content/_index.md $ hugo new content/_index.md
@ -73,20 +84,101 @@ Voila! You can see your new site at `http://localhost:1313/`.
{{% /steps %}} {{% /steps %}}
## Update Theme
{{% details title="How to update theme?" %}} {{% details title="How to update theme?" %}}
To update the theme to the [latest released version](https://github.com/imfing/hextra/releases), run the following command: To update all Hugo modules in your project to their latest versions, run the following command:
```shell ```shell
$ hugo mod get -u $ hugo mod get -u
``` ```
To update only Hextra to the [latest released version](https://github.com/imfing/hextra/releases), run the following command:
```shell
hugo mod get -u github.com/imfing/hextra
```
See [Hugo Modules](https://gohugo.io/hugo-modules/use-modules/#update-all-modules) for more details. See [Hugo Modules](https://gohugo.io/hugo-modules/use-modules/#update-all-modules) for more details.
{{% /details %}} {{% /details %}}
### Setup Hextra as Git submodule
#### Prerequisites
Before starting, you need to have the following softwares installed:
- [Hugo (extended version)](https://gohugo.io/installation/)
- [Git](https://git-scm.com/)
#### Steps
{{% steps %}}
### Initialize a new Hugo site
```shell
$ hugo new site my-site --format=yaml
```
### Add Hextra theme as a Git submodule
```shell
git submodule add https://github.com/imfing/hextra.git themes/hextra
```
Configure `hugo.yaml` to use Hextra theme by adding the following:
```yaml
theme: hextra
```
### Create your first content pages
Let's create new content page for the home page and the documentation page:
```shell
$ hugo new content/_index.md
$ hugo new content/docs/_index.md
```
### Preview the site locally
```shell
$ hugo server --buildDrafts --disableFastRender
```
Voila! You can see your new site at `http://localhost:1313/`.
{{% /steps %}}
When using [CI/CD](https://en.wikipedia.org/wiki/CI/CD) for Hugo website deployment, it's essential to ensure that the following command is executed before running the `hugo` command.
```shell
git submodule update --init
```
Failure to run this command will result in the theme folder not being populated with Hextra theme files, leading to a build failure.
{{% details title="How to update theme?" %}}
To update all submodules in your repository to their latest commits, run the following command:
```shell
$ git submodule update --remote
```
To update only Hextra to the latest commit, run the following command:
```shell
git submodule update --remote themes/hextra
```
See [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) for more details.
{{% /details %}}
## Next ## Next

View File

@ -5,7 +5,7 @@ weight: 2
Hugo reads its configuration from `hugo.yaml` in the root of your Hugo site. Hugo reads its configuration from `hugo.yaml` in the root of your Hugo site.
The config file is where you can configure all aspects of your site. The config file is where you can configure all aspects of your site.
You can find the config file for this site in `exampleSite/hugo.yaml` as a good starting point. Check out the config file for this site [`exampleSite/hugo.yaml`](https://github.com/imfing/hextra/blob/main/exampleSite/hugo.yaml) on GitHub to get a comprehensive idea of available settings and best practices.
<!--more--> <!--more-->
@ -181,6 +181,26 @@ Include both `favicon.ico` and `favicon.svg` files in your project to ensure you
While `favicon.ico` is generally for older browsers, `favicon.svg` is supported by modern ones. The optional `favicon-dark.svg` can be included for a tailored experience in dark mode. While `favicon.ico` is generally for older browsers, `favicon.svg` is supported by modern ones. The optional `favicon-dark.svg` can be included for a tailored experience in dark mode.
Feel free to use tools like [favicon.io](https://favicon.io/) or [favycon](https://github.com/ruisaraiva19/favycon) to generate these icons. Feel free to use tools like [favicon.io](https://favicon.io/) or [favycon](https://github.com/ruisaraiva19/favycon) to generate these icons.
### Theme Configuration
Use the `theme` setting to configure the default theme mode and toggle button, allowing visitors to switch between light or dark mode.
```yaml {filename="hugo.yaml"}
params:
theme:
# light | dark | system
default: system
displayToggle: true
```
Options for `theme.default`:
- `light` - always use light mode
- `dark` - always use dark mode
- `system` - sync with the operating system setting (default)
The `theme.displayToggle` parameter allows you to display a toggle button for changing themes.
When set to `true`, visitors can switch between light or dark mode, overriding the default setting.
### Page Width ### Page Width
@ -193,11 +213,42 @@ params:
width: wide width: wide
``` ```
There are three available options: `full`, `wide`, and `normal`. There are three available options: `full`, `wide`, and `normal`. By default, the page width is set to `normal`.
By default, the page width is set to `normal`.
Similarly, the width of the navbar and footer can be customized by the `params.navbar.width` and `params.footer.width` parameters. Similarly, the width of the navbar and footer can be customized by the `params.navbar.width` and `params.footer.width` parameters.
### Search Index
Full-text search powered by [FlexSearch](https://github.com/nextapps-de/flexsearch) is enabled by default.
To customize the search index, set the `params.search.flexsearch.index` parameter in the config file:
```yaml {filename="hugo.yaml"}
params:
# Search
search:
enable: true
type: flexsearch
flexsearch:
# index page by: content | summary | heading | title
index: content
```
Options for `flexsearch.index`:
- `content` - full content of the page (default)
- `summary` - summary of the page, see [Hugo Content Summaries](https://gohugo.io/content-management/summaries/) for more details
- `heading` - level 1 and level 2 headings
- `title` - only include the page title
To exclude a page from the search index, set the `excludeSearch: true` in the front matter of the page:
```yaml {filename="content/docs/guide/configuration.md"}
---
title: Configuration
excludeSearch: true
---
```
### Google Analytics ### Google Analytics

View File

@ -26,6 +26,7 @@ languages:
title: "Hextra テーマ" title: "Hextra テーマ"
zh-cn: zh-cn:
languageName: 简体中文 languageName: 简体中文
languageCode: zh-CN
weight: 3 weight: 3
title: Hextra title: Hextra
@ -108,6 +109,11 @@ params:
# full (100%), wide (90rem), normal (1280px) # full (100%), wide (90rem), normal (1280px)
width: normal width: normal
theme:
# light | dark | system
default: system
displayToggle: true
footer: footer:
displayCopyright: true displayCopyright: true
displayPoweredBy: true displayPoweredBy: true
@ -116,9 +122,33 @@ params:
displayUpdatedDate: true displayUpdatedDate: true
dateFormat: "January 2, 2006" dateFormat: "January 2, 2006"
# Search
# flexsearch is enabled by default
search: search:
enable: true enable: true
type: flexsearch
flexsearch:
# index page by: content | summary | heading | title
index: content
editURL: editURL:
enable: true enable: true
base: "https://github.com/imfing/hextra/edit/main/exampleSite/content" base: "https://github.com/imfing/hextra/edit/main/exampleSite/content"
comments:
enable: false
type: giscus
# https://giscus.app/
giscus:
repo: imfing/hextra
repoId: R_kgDOJ9fJag
category: General
categoryId: DIC_kwDOJ9fJas4CY7gW
# mapping: pathname
# strict: 0
# reactionsEnabled: 1
# emitMetadata: 0
# inputPosition: top
# lang: en

View File

@ -112,6 +112,7 @@
"border-black/5", "border-black/5",
"border-blue-200", "border-blue-200",
"border-gray-200", "border-gray-200",
"border-gray-500",
"border-l", "border-l",
"border-orange-100", "border-orange-100",
"border-red-200", "border-red-200",
@ -127,6 +128,7 @@
"content", "content",
"contrast-more:border", "contrast-more:border",
"contrast-more:border-current", "contrast-more:border-current",
"contrast-more:border-gray-800",
"contrast-more:border-gray-900", "contrast-more:border-gray-900",
"contrast-more:border-neutral-400", "contrast-more:border-neutral-400",
"contrast-more:border-primary-500", "contrast-more:border-primary-500",
@ -152,6 +154,7 @@
"contrast-more:text-gray-800", "contrast-more:text-gray-800",
"contrast-more:text-gray-900", "contrast-more:text-gray-900",
"contrast-more:underline", "contrast-more:underline",
"copy-icon",
"cursor-default", "cursor-default",
"cursor-pointer", "cursor-pointer",
"dark:before:bg-neutral-800", "dark:before:bg-neutral-800",
@ -171,6 +174,7 @@
"dark:block", "dark:block",
"dark:border-blue-200/30", "dark:border-blue-200/30",
"dark:border-gray-100/20", "dark:border-gray-100/20",
"dark:border-gray-400",
"dark:border-neutral-700", "dark:border-neutral-700",
"dark:border-neutral-800", "dark:border-neutral-800",
"dark:border-orange-400/30", "dark:border-orange-400/30",
@ -188,6 +192,7 @@
"dark:hover:bg-neutral-900", "dark:hover:bg-neutral-900",
"dark:hover:bg-primary-100/5", "dark:hover:bg-primary-100/5",
"dark:hover:bg-primary-700", "dark:hover:bg-primary-700",
"dark:hover:border-gray-100",
"dark:hover:border-gray-600", "dark:hover:border-gray-600",
"dark:hover:border-neutral-500", "dark:hover:border-neutral-500",
"dark:hover:border-neutral-700", "dark:hover:border-neutral-700",
@ -226,6 +231,7 @@
"data-[state=selected]:text-primary-600", "data-[state=selected]:text-primary-600",
"decoration-from-font", "decoration-from-font",
"duration-200", "duration-200",
"duration-75",
"ease-in", "ease-in",
"ease-in-out", "ease-in-out",
"filename", "filename",
@ -264,6 +270,7 @@
"h-0", "h-0",
"h-16", "h-16",
"h-2", "h-2",
"h-3.5",
"h-4", "h-4",
"h-5", "h-5",
"h-7", "h-7",
@ -289,6 +296,7 @@
"hover:border-gray-200", "hover:border-gray-200",
"hover:border-gray-300", "hover:border-gray-300",
"hover:border-gray-400", "hover:border-gray-400",
"hover:border-gray-900",
"hover:dark:bg-primary-500/10", "hover:dark:bg-primary-500/10",
"hover:dark:text-primary-600", "hover:dark:text-primary-600",
"hover:opacity-60", "hover:opacity-60",

View File

@ -1,5 +1,9 @@
onThisPage: "On this page" backToTop: "Scroll to top"
changeLanguage: "Change language"
changeTheme: "Change theme"
copyright: "© 2023 Hextra Project."
editThisPage: "Edit this page on GitHub →" editThisPage: "Edit this page on GitHub →"
lastUpdated: "Last updated on" lastUpdated: "Last updated on"
onThisPage: "On this page"
copyright: "© 2023 Hextra Project." readMore: "Read more →"
searchPlaceholder: "Search..."

7
i18n/es.yaml Normal file
View File

@ -0,0 +1,7 @@
onThisPage: "En esta página"
editThisPage: "Edita esta página en GitHub →"
lastUpdated: "Última actualización"
backToTop: "Subir al inicio"
copyright: "© 2023 Hextra Project."

9
i18n/ko.yaml Normal file
View File

@ -0,0 +1,9 @@
backToTop: "맨위로 스크롤"
changeLanguage: "언어변경"
changeTheme: "테마변경"
copyright: "© 2023 Hextra Project."
editThisPage: "Github에서 편집하기 →"
lastUpdated: "마지막 수정일자"
onThisPage: "페이지 목차"
readMore: "더보기 →"
searchPlaceholder: "검색..."

7
i18n/pt.yaml Normal file
View File

@ -0,0 +1,7 @@
onThisPage: "Nesta página"
editThisPage: "Edita esta página no GitHub →"
lastUpdated: "Última actualização"
backToTop: "Voltar ao topo"
copyright: "© 2023 Projecto Hextra."

5
i18n/sw.yaml Normal file
View File

@ -0,0 +1,5 @@
onThisPage: "Kwenye ukurasa huu"
editThisPage: "Hariri ukurasa huu kwenye GitHub →"
lastUpdated: "Ilisasishwa mwisho"
backToTop: "Tembeza hadi juu"
copyright: "© 2023 Hextra Project."

7
i18n/vi.yaml Normal file
View File

@ -0,0 +1,7 @@
onThisPage: "Ở trang này"
editThisPage: "Sửa trang này trên GitHub →"
lastUpdated: "Lần cuối cập nhật lúc"
backToTop: "Lướt lên đầu trang"
copyright: "© 2023 Hextra Project."

View File

@ -12,4 +12,6 @@ onThisPage: "此页上"
editThisPage: "在 GitHub 上编辑此页 →" editThisPage: "在 GitHub 上编辑此页 →"
lastUpdated: "最后更新于" lastUpdated: "最后更新于"
backToTop: "返回顶部"
copyright: "© 2023 Hextra Project." copyright: "© 2023 Hextra Project."

View File

@ -15,9 +15,13 @@
<pre><code id="code-block-{{ .Ordinal }}">{{ .Inner }}</code></pre> <pre><code id="code-block-{{ .Ordinal }}">{{ .Inner }}</code></pre>
{{- end -}} {{- end -}}
<div class="opacity-0 transition group-hover/code:opacity-100 flex gap-1 absolute m-[11px] right-0 {{ if $filename }}top-8{{ else }}top-0{{ end }}"> <div class="opacity-0 transition group-hover/code:opacity-100 flex gap-1 absolute m-[11px] right-0 {{ if $filename }}top-8{{ else }}top-0{{ end }}">
<button class="code-copy-btn group/copybtn transition-all active:opacity-50 bg-primary-700/5 border border-black/5 text-gray-600 hover:text-gray-900 rounded-md p-1.5 dark:bg-primary-300/10 dark:border-white/10 dark:text-gray-400 dark:hover:text-gray-50" title="Copy code" data-clipboard-target="#code-block-{{ .Ordinal }}"> <button
{{ partial "utils/icon.html" (dict "name" "copy" "attributes" "class=\"group-[.copied]/copybtn:hidden pointer-events-none h-4 w-4\"") }} class="code-copy-btn group/copybtn transition-all active:opacity-50 bg-primary-700/5 border border-black/5 text-gray-600 hover:text-gray-900 rounded-md p-1.5 dark:bg-primary-300/10 dark:border-white/10 dark:text-gray-400 dark:hover:text-gray-50"
{{ partial "utils/icon.html" (dict "name" "check" "attributes" "class=\"hidden group-[.copied]/copybtn:block success-icon pointer-events-none h-4 w-4\"") }} title="Copy code"
data-clipboard-target="#code-block-{{ .Ordinal }}"
>
<div class="group-[.copied]/copybtn:hidden copy-icon pointer-events-none h-4 w-4"></div>
<div class="hidden group-[.copied]/copybtn:block success-icon pointer-events-none h-4 w-4"></div>
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,10 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}"> <html lang="{{ .Site.Language.Lang }}">
{{- partial "head.html" . -}} {{ partial "head.html" . -}}
<body dir="ltr"> <body dir="ltr">
{{- partial "navbar.html" . -}} {{ partial "navbar.html" . -}}
{{- block "main" . }}{{ end -}} {{- block "main" . }}{{ end -}}
{{- if (.Site.Params.footer.enable | default true) }}{{ partial "footer.html" . }}{{ end }} {{- if or (eq .Site.Params.footer.enable nil) (.Site.Params.footer.enable) }}
{{ partial "footer.html" . }}
{{ end -}}
</body> </body>
{{ partial "scripts.html" . }} {{ partialCached "scripts.html" . }}
{{ partial "third-party/scripts.html" . }}
</html> </html>

View File

@ -10,6 +10,7 @@
</div> </div>
<div class="mt-16"></div> <div class="mt-16"></div>
{{ partial "components/last-updated.html" . }} {{ partial "components/last-updated.html" . }}
{{ partial "components/comments.html" . }}
</main> </main>
</article> </article>
</div> </div>

View File

@ -10,6 +10,8 @@
<div class="content"> <div class="content">
{{ .Content }} {{ .Content }}
</div> </div>
<div class="mt-16"></div>
{{ partial "components/comments.html" . }}
</main> </main>
</article> </article>
</div> </div>

View File

@ -1,4 +1,5 @@
{{ define "main" }} {{ define "main" }}
{{- $readMore := (T "readMore") | default "Read more →" -}}
<div class='mx-auto flex {{ partial "utils/page-width" . }}'> <div class='mx-auto flex {{ partial "utils/page-width" . }}'>
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
<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)]"> <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)]">
@ -11,7 +12,7 @@
<h3><a style="color: inherit; text-decoration: none;" class="block font-semibold mt-8 text-2xl " href="{{ .RelPermalink }}">{{ .Title }}</a></h3> <h3><a style="color: inherit; text-decoration: none;" class="block font-semibold mt-8 text-2xl " href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
<p class="opacity-80 mt-6 leading-7"> <p class="opacity-80 mt-6 leading-7">
{{- partial "utils/page-description" . }} {{- partial "utils/page-description" . }}
<span class="inline-block"> <a class="text-[color:hsl(var(--primary-hue),100%,50%)] underline underline-offset-2 decoration-from-font" href="{{ .RelPermalink }}">Read more </a> </span> <span class="inline-block"> <a class="text-[color:hsl(var(--primary-hue),100%,50%)] underline underline-offset-2 decoration-from-font" href="{{ .RelPermalink }}">{{ $readMore }}</a> </span>
</p> </p>
<p class="opacity-50 text-sm mt-6 leading-7">{{ partial "utils/format-date" .Date }}</p> <p class="opacity-50 text-sm mt-6 leading-7">{{ partial "utils/format-date" .Date }}</p>
</div> </div>

View File

@ -28,6 +28,7 @@
{{ partial "components/last-updated.html" . }} {{ partial "components/last-updated.html" . }}
{{ .Scratch.Set "reversePagination" true }} {{ .Scratch.Set "reversePagination" true }}
{{ partial "components/pager.html" . }} {{ partial "components/pager.html" . }}
{{ partial "components/comments.html" . }}
</main> </main>
</article> </article>
</div> </div>

View File

@ -9,9 +9,9 @@
<h1>{{ .Title }}</h1> <h1>{{ .Title }}</h1>
{{ .Content }} {{ .Content }}
</div> </div>
<div class="mt-16"></div>
{{ partial "components/last-updated.html" . }} {{ partial "components/last-updated.html" . }}
{{ partial "components/pager.html" . }} {{ partial "components/pager.html" . }}
{{ partial "components/comments.html" . }}
</main> </main>
</article> </article>
</div> </div>

View File

@ -11,6 +11,7 @@
</div> </div>
{{ partial "components/last-updated.html" . }} {{ partial "components/last-updated.html" . }}
{{ partial "components/pager.html" . }} {{ partial "components/pager.html" . }}
{{ partial "components/comments.html" . }}
</main> </main>
</article> </article>
</div> </div>

View File

@ -0,0 +1,11 @@
{{- $enableComments := site.Params.comments.enable | default false -}}
{{ if not (eq .Params.comments nil) }}
{{ $enableComments = .Params.comments }}
{{ end }}
{{- if $enableComments -}}
{{- if eq site.Params.comments.type "giscus" -}}
{{ partial "components/giscus.html" . }}
{{- end -}}
{{- end -}}

View File

@ -0,0 +1,62 @@
{{- $lang := site.Language.LanguageCode | default `en` -}}
{{- with site.Params.comments.giscus -}}
<script>
/*
* "preferred color scheme" theme in giscus works using "prefers-color-scheme" in media query.
* but, hugo's theme switch function works by using "color-theme" in local storage.
* This solution was created with reference to:
* https://github.com/giscus/giscus/issues/336#issuecomment-1214366281
*/
function getGiscusTheme() {
return localStorage.getItem("color-theme");
}
function setGiscusTheme() {
function sendMessage(message) {
const iframe = document.querySelector('iframe.giscus-frame');
if (!iframe) return;
iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app');
}
sendMessage({
setConfig: {
theme: getGiscusTheme(),
},
});
}
document.addEventListener('DOMContentLoaded', function () {
const giscusAttributes = {
"src": "https://giscus.app/client.js",
"data-repo": "{{ .repo }}",
"data-repo-id": "{{ .repoId }}",
"data-category": "{{ .category }}",
"data-category-id": "{{ .categoryId }}",
"data-mapping": "{{ .mapping | default `pathname` }}",
"data-strict": "{{ (string .strict) | default 0 }}",
"data-reactions-enabled": "{{ (string .reactionsEnabled) | default 1 }}",
"data-emit-metadata": "{{ (string .emitMetadata) | default 0 }}",
"data-input-position": "{{ .inputPosition | default `top` }}",
"data-theme": getGiscusTheme(),
"data-lang": "{{ .lang | default $lang }}",
"crossorigin": "anonymous",
"async": "",
};
// Dynamically create script tag
const giscusScript = document.createElement("script");
Object.entries(giscusAttributes).forEach(([key, value]) => giscusScript.setAttribute(key, value));
document.getElementById('giscus').appendChild(giscusScript);
// Update giscus theme when theme switcher is clicked
const toggles = document.querySelectorAll(".theme-toggle");
if (toggles) {
toggles.forEach(toggle => toggle.addEventListener('click', setGiscusTheme));
}
});
</script>
<div id="giscus"></div>
{{- else -}}
{{ warnf "giscus is not configured" }}
{{- end -}}

View File

@ -5,15 +5,23 @@
{{- $prev := cond $reversePagination .PrevInSection .NextInSection -}} {{- $prev := cond $reversePagination .PrevInSection .NextInSection -}}
{{- $next := cond $reversePagination .NextInSection .PrevInSection -}} {{- $next := cond $reversePagination .NextInSection .PrevInSection -}}
{{- with .Params.prev -}} {{- if eq .Params.prev false }}
{{- with $.Site.GetPage . -}} {{- if $reversePagination }}{{ $next = false }}{{ else }}{{ $prev = false }}{{ end -}}
{{- if $reversePagination }}{{ $next = . }}{{ else }}{{ $prev = . }}{{ end -}} {{ else }}
{{- with .Params.prev -}}
{{- with $.Site.GetPage . -}}
{{- if $reversePagination }}{{ $next = . }}{{ else }}{{ $prev = . }}{{ end -}}
{{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- with .Params.next -}} {{- if eq .Params.next false }}
{{- with $.Site.GetPage . -}} {{- if $reversePagination }}{{ $prev = false }}{{ else }}{{ $next = false }}{{ end -}}
{{- if $reversePagination }}{{ $prev = . }}{{ else }}{{ $next = . }}{{ end -}} {{ else }}
{{- with .Params.next -}}
{{- with $.Site.GetPage . -}}
{{- if $reversePagination }}{{ $prev = . }}{{ else }}{{ $next = . }}{{ end -}}
{{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}

View File

@ -1,4 +1,5 @@
{{- $enableFooterSwitches := .Scratch.Get "enableFooterSwitches" | default false -}} {{- $enableFooterSwitches := .Scratch.Get "enableFooterSwitches" | default false -}}
{{- $displayThemeToggle := site.Params.theme.displayToggle | default true -}}
{{- $copyright := (T "copyright") | default "© 2023 Hextra." -}} {{- $copyright := (T "copyright") | default "© 2023 Hextra." -}}
@ -11,20 +12,23 @@
{{ end -}} {{ end -}}
{{- end -}} {{- end -}}
<footer class="hextra-footer bg-gray-100 pb-[env(safe-area-inset-bottom)] dark:bg-neutral-900 print:bg-transparent"> <footer class="hextra-footer bg-gray-100 pb-[env(safe-area-inset-bottom)] dark:bg-neutral-900 print:bg-transparent">
{{- if $enableFooterSwitches }} {{- if $enableFooterSwitches -}}
<div class='mx-auto flex gap-2 py-2 px-4 {{ $footerWidth }}'> <div class="mx-auto flex gap-2 py-2 px-4 {{ $footerWidth }}">
{{- partial "language-switch.html" (dict "context" .) -}} {{- partial "language-switch.html" (dict "context" .) -}}
{{- partial "theme-toggle.html" -}} {{- with $displayThemeToggle }}{{ partial "theme-toggle.html" }}{{ end -}}
</div> </div>
{{ end -}} {{- if or site.IsMultiLingual $displayThemeToggle -}}
<hr class="dark:border-neutral-800" /> <hr class="dark:border-neutral-800" />
{{- end -}}
{{- end -}}
<div <div
class='{{ $footerWidth }} mx-auto flex justify-center py-12 pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-right),1.5rem)] text-gray-600 dark:text-gray-400 md:justify-start' class="{{ $footerWidth }} mx-auto flex justify-center py-12 pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-right),1.5rem)] text-gray-600 dark:text-gray-400 md:justify-start"
> >
<div class="flex w-full flex-col items-center sm:items-start"> <div class="flex w-full flex-col items-center sm:items-start">
{{- if (.Site.Params.footer.displayPoweredBy | default true) }}<div class="font-semibold">{{ template "theme-credit" . }}</div>{{ end }} {{- if (.Site.Params.footer.displayPoweredBy | default true) }}<div class="font-semibold">{{ template "theme-credit" . }}</div>{{ end }}
{{- if .Site.Params.footer.displayCopyright }}<p class="mt-6 text-xs">{{ $copyright | markdownify }}</p>{{ end }} {{- if .Site.Params.footer.displayCopyright }}<div class="mt-6 text-xs">{{ $copyright | markdownify }}</div>{{ end }}
</div> </div>
</div> </div>
</footer> </footer>

View File

@ -33,13 +33,25 @@
<script> <script>
/* Initialize light/dark mode */ /* Initialize light/dark mode */
if (localStorage.getItem("color-theme") === "dark" || (!("color-theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches)) { const defaultTheme = '{{ site.Params.theme.default | default `system`}}';
const setDarkTheme = () => {
document.documentElement.classList.add("dark"); document.documentElement.classList.add("dark");
document.documentElement.style.colorScheme = "dark"; document.documentElement.style.colorScheme = "dark";
} else { }
const setLightTheme = () => {
document.documentElement.classList.remove("dark"); document.documentElement.classList.remove("dark");
document.documentElement.style.colorScheme = "light"; document.documentElement.style.colorScheme = "light";
} }
if ("color-theme" in localStorage) {
localStorage.getItem("color-theme") === "dark" ? setDarkTheme() : setLightTheme();
} else {
defaultTheme === "dark" ? setDarkTheme() : setLightTheme();
if (defaultTheme === "system") {
window.matchMedia("(prefers-color-scheme: dark)").matches ? setDarkTheme() : setLightTheme();
}
}
</script> </script>
{{ partial "custom/head-end.html" . }} {{ partial "custom/head-end.html" . }}

View File

@ -30,7 +30,7 @@
{{- $currentPage := . -}} {{- $currentPage := . -}}
{{- range .Site.Menus.main -}} {{- range .Site.Menus.main -}}
{{- if eq .Params.type "search" -}} {{- if eq .Params.type "search" -}}
{{- partial "search.html" (dict "params" .Params) -}} {{- partialCached "search.html" $currentPage -}}
{{- else -}} {{- else -}}
{{- $external := strings.HasPrefix .URL "http" -}} {{- $external := strings.HasPrefix .URL "http" -}}
{{/* Display icon menu item */}} {{/* Display icon menu item */}}

View File

@ -1,68 +1,32 @@
{{- $jsTheme := resources.Get "js/theme.js" -}} {{- $jsTheme := resources.Get "js/theme.js" | resources.ExecuteAsTemplate "theme.js" . -}}
{{- $jsMenu := resources.Get "js/menu.js" -}} {{- $jsMenu := resources.Get "js/menu.js" -}}
{{- $jsTabs := resources.Get "js/tabs.js" -}} {{- $jsTabs := resources.Get "js/tabs.js" -}}
{{- $jsLang := resources.Get "js/lang.js" -}} {{- $jsLang := resources.Get "js/lang.js" -}}
{{- $jsCodeCopy := resources.Get "js/code-copy.js" -}} {{- $jsCodeCopy := resources.Get "js/code-copy.js" -}}
{{- $jsFileTree := resources.Get "js/filetree.js" -}} {{- $jsFileTree := resources.Get "js/filetree.js" -}}
{{- $jsSidebar := resources.Get "js/sidebar.js" -}} {{- $jsSidebar := resources.Get "js/sidebar.js" -}}
{{- $jsBackToTop := resources.Get "js/back-to-top.js" -}}
{{- $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs $jsLang $jsFileTree $jsSidebar | resources.Concat "js/main.js" -}} {{- $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs $jsLang $jsFileTree $jsSidebar $jsBackToTop | resources.Concat "js/main.js" -}}
{{- if hugo.IsProduction -}} {{- if hugo.IsProduction -}}
{{- $scripts = $scripts | minify | fingerprint -}} {{- $scripts = $scripts | minify | fingerprint -}}
{{- end -}} {{- end -}}
<script defer src="{{ $scripts.RelPermalink }}" integrity="{{ $scripts.Data.Integrity }}"></script> <script defer src="{{ $scripts.RelPermalink }}" integrity="{{ $scripts.Data.Integrity }}"></script>
{{/* FlexSearch */}}
{{/* Search */}}
{{- if (site.Params.search.enable | default true) -}} {{- if (site.Params.search.enable | default true) -}}
{{- $jsSearchScript := printf "%s.search.js" .Language.Lang -}} {{- $searchType := site.Params.search.type | default "flexsearch" -}}
{{- $jsSearch := resources.Get "js/flexsearch.js" | resources.ExecuteAsTemplate $jsSearchScript . -}} {{- if eq $searchType "flexsearch" -}}
{{- if hugo.IsProduction -}} {{- $jsSearchScript := printf "%s.search.js" .Language.Lang -}}
{{- $jsSearch = $jsSearch | minify | fingerprint -}} {{- $jsSearch := resources.Get "js/flexsearch.js" | resources.ExecuteAsTemplate $jsSearchScript . -}}
{{- if hugo.IsProduction -}}
{{- $jsSearch = $jsSearch | minify | fingerprint -}}
{{- end -}}
{{- $flexSearchJS := resources.Get "lib/flexsearch/flexsearch.bundle.min.js" | fingerprint -}}
<script defer src="{{ $flexSearchJS.RelPermalink }}" integrity="{{ $flexSearchJS.Data.Integrity }}"></script>
<script defer src="{{ $jsSearch.RelPermalink }}" integrity="{{ $jsSearch.Data.Integrity }}"></script>
{{- else -}}
{{- errorf `search type "%s" is not supported` $searchType -}}
{{- end -}} {{- end -}}
{{- $flexSearchJS := resources.Get "lib/flexsearch/flexsearch.bundle.min.js" | fingerprint -}}
<script defer src="{{ $flexSearchJS.RelPermalink }}" integrity="{{ $flexSearchJS.Data.Integrity }}"></script>
<script defer src="{{ $jsSearch.RelPermalink }}" integrity="{{ $jsSearch.Data.Integrity }}"></script>
{{- end -}} {{- end -}}
{{/* Mermaid */}}
{{/* FIXME: need to investigate .Page.Store hasMermaid is set for homepage */}}
{{- if and (.Page.Store.Get "hasMermaid") (not .Page.IsHome) -}}
{{- $mermaidJS := resources.Get "lib/mermaid/mermaid.min.js" | fingerprint -}}
<script defer src="{{ $mermaidJS.RelPermalink }}" integrity="{{ $mermaidJS.Data.Integrity }}"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const theme = document.documentElement.classList.contains("dark") ? "dark" : "default";
mermaid.initialize({ startOnLoad: true, theme: theme });
});
</script>
{{- end -}}
{{/* KaTex */}}
{{- if .Page.Params.math -}}
{{- $katexCSS := resources.Get "lib/katex/katex.min.css" | fingerprint -}}
{{- $katexJS := resources.Get "lib/katex/katex.min.js" | fingerprint -}}
{{- $mhchemJS := resources.Get "lib/katex/mhchem.min.js" | fingerprint -}}
{{- $katexAutoRenderJS := resources.Get "lib/katex/auto-render.min.js" | fingerprint -}}
<link type="text/css" rel="stylesheet" href="{{ $katexCSS.RelPermalink }}" integrity="{{ $katexCSS.Data.Integrity }}" />
<script defer src="{{ $katexJS.RelPermalink }}" integrity="{{ $katexJS.Data.Integrity }}"></script>
<script defer src="{{ $katexAutoRenderJS.RelPermalink }}" integrity="{{ $katexAutoRenderJS.Data.Integrity }}"></script>
<script defer src="{{ $mhchemJS.RelPermalink }}" integrity="{{ $mhchemJS.Data.Integrity }}"></script>
{{ $katexFonts := resources.Match "lib/katex/fonts/*" }}
{{- range $katexFonts -}}
{{ .Publish }}
{{- end -}}
<script>
// TODO: make render options configurable
document.addEventListener("DOMContentLoaded", function () {
renderMathInElement(document.body, {
delimiters: [
{ left: "$$", right: "$$", display: true },
{ left: "$", right: "$", display: false },
{ left: "\\(", right: "\\)", display: false },
{ left: "\\[", right: "\\]", display: true },
],
throwOnError: false,
});
});
</script>
{{ end }}

View File

@ -13,7 +13,7 @@
<kbd <kbd
class="absolute my-1.5 select-none ltr:right-1.5 rtl:left-1.5 h-5 rounded bg-white px-1.5 font-mono text-[10px] font-medium text-gray-500 border dark:border-gray-100/20 dark:bg-dark/50 contrast-more:border-current contrast-more:text-current contrast-more:dark:border-current items-center gap-1 transition-opacity pointer-events-none hidden sm:flex" class="absolute my-1.5 select-none ltr:right-1.5 rtl:left-1.5 h-5 rounded bg-white px-1.5 font-mono text-[10px] font-medium text-gray-500 border dark:border-gray-100/20 dark:bg-dark/50 contrast-more:border-current contrast-more:text-current contrast-more:dark:border-current items-center gap-1 transition-opacity pointer-events-none hidden sm:flex"
> >
<span class="text-xs"></span>K CTRL K
</kbd> </kbd>
</div> </div>

View File

@ -6,134 +6,40 @@
{{- $sidebarClass := cond $disableSidebar (cond $displayPlaceholder "md:hidden xl:block" "md:hidden") "md:sticky" -}} {{- $sidebarClass := cond $disableSidebar (cond $displayPlaceholder "md:hidden xl:block" "md:hidden") "md:sticky" -}}
{{- $navRoot := cond (eq site.Home.Type "docs") site.Home $context.FirstSection -}} {{- $navRoot := cond (eq site.Home.Type "docs") site.Home $context.FirstSection -}}
{{- $navPages := union $navRoot.RegularPages $navRoot.Sections -}}
{{- $pageURL := $context.RelPermalink -}} {{- $pageURL := $context.RelPermalink -}}
<aside class="sidebar-container flex flex-col print:hidden md:top-16 md:shrink-0 md:w-64 md:self-start max-md:[transform:translate3d(0,-100%,0)] {{ $sidebarClass }}"> <aside class="sidebar-container flex flex-col print:hidden md:top-16 md:shrink-0 md:w-64 md:self-start max-md:[transform:translate3d(0,-100%,0)] {{ $sidebarClass }}">
<!-- Search bar on small screen --> {{/* Search bar on small screen */}}
<div class="px-4 pt-4 md:hidden"> {{- partialCached "sidebar/mobile-search" . -}}
{{ partial "search.html" }}
</div>
<div class="hextra-scrollbar overflow-y-auto overflow-x-hidden p-4 grow md:h-[calc(100vh-var(--navbar-height)-var(--menu-height))]"> <div class="hextra-scrollbar overflow-y-auto overflow-x-hidden p-4 grow md:h-[calc(100vh-var(--navbar-height)-var(--menu-height))]">
<ul class="flex flex-col gap-1 md:hidden"> <ul class="flex flex-col gap-1 md:hidden">
<!-- Nav --> {{/* Mobile Navigation */}}
{{ template "sidebar-main" (dict "context" site.Home "pageURL" $pageURL "page" $context "toc" true) -}} {{ $treeMobile := partialCached "sidebar/section-tree" site.Home site.Home }}
{{ template "sidebar-footer" }} {{ partial "sidebar/render-tree" (dict "context" site.Home "page" $context "tree" ($treeMobile | unmarshal)) }}
{{ partialCached "sidebar/extra" $context }}
</ul> </ul>
<!-- Sidebar on large screen --> {{/* Sidebar on large screen */}}
{{- if $disableSidebar -}} <ul class="flex flex-col gap-1 max-md:hidden">
{{- if $displayPlaceholder }}<div class="max-xl:hidden h-0 w-64 shrink-0"></div>{{ end -}} {{ $tree := partialCached "sidebar/section-tree" $navRoot $navRoot }}
{{ .context.Scratch.Set "enableFooterSwitches" true }} {{ partial "sidebar/render-tree" (dict "context" $navRoot "page" $context "tree" ($tree | unmarshal)) }}
{{- else -}} {{ partialCached "sidebar/extra" $context }}
<ul class="flex flex-col gap-1 max-md:hidden"> </ul>
{{ 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 "md:hidden" "" }}
<div class="{{ $switchesClass }} {{ with site.IsMultiLingual }}justify-end{{ end }} sticky bottom-0 bg-white dark:bg-dark mx-4 py-4 shadow-[0_-12px_16px_#fff] flex items-center gap-2 dark:border-neutral-800 dark:shadow-[0_-12px_16px_#111] contrast-more:border-neutral-400 contrast-more:shadow-none contrast-more:dark:shadow-none border-t" data-toggle-animation="show">
{{- with site.IsMultiLingual }}
{{ partial "language-switch" (dict "context" $context "grow" true) }}
{{ partial "theme-toggle" (dict "hideLabel" true) }}
{{ else }}
<div class="flex grow flex-col">
{{ partial "theme-toggle" }}
</div>
{{ end -}}
</div> </div>
{{ partial "sidebar/switches" (dict "context" $context "disableSidebar" $disableSidebar) }}
</aside> </aside>
{{- define "sidebar-main" -}} {{- define "partials/sidebar/mobile-search" -}}
{{ template "sidebar-tree" (dict "context" .context "level" 0 "page" .page "pageURL" .pageURL "toc" (.toc | default false)) }} <div class="px-4 pt-4 md:hidden">
{{- end -}} {{- partialCached "search.html" . -}}
</div>
{{- 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 -}}
{{- if eq $level 0 -}}
{{- range $items.ByWeight }}
{{- $active := eq $pageURL .RelPermalink -}}
{{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }}
<li class="{{ 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 -}}
{{- else -}}
<div class="pt-1 ltr:pr-0 overflow-hidden transition-all ease-in-out duration-200">
<ul class='relative flex flex-col gap-1 before:absolute before:inset-y-1 before:w-px before:bg-gray-200 before:content-[""] ltr:ml-3 ltr:pl-3 ltr:before:left-0 rtl:mr-3 rtl:pr-3 rtl:before:right-0 dark:before:bg-neutral-800'>
{{- range $items.ByWeight }}
{{- $active := eq $pageURL .RelPermalink -}}
{{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }}
{{- $title := .LinkTitle | default .File.BaseFileName -}}
<li class="flex flex-col gap-1 {{ if $shouldOpen }}open{{ end }}">
{{- template "sidebar-item-link" dict "context" . "active" $active "title" $title "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='flex flex-col gap-1 relative before:absolute before:inset-y-1 before:w-px before:bg-gray-200 before:content-[""] dark:before:bg-neutral-800 ltr:pl-3 ltr:before:left-0 rtl:pr-3 rtl:before:right-0 ltr:ml-3 rtl:mr-3'>
{{- range . }}
{{- with .Headings }}
{{- range . -}}
<li>
<a
href="#{{ anchorize .ID }}"
class="flex rounded px-2 py-1.5 text-sm transition-colors [word-break:break-word] cursor-pointer [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] contrast-more:border flex gap-2 before:opacity-25 before:content-['#'] text-gray-500 hover:bg-gray-100 hover:text-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:text-gray-900 contrast-more:dark:text-gray-50 contrast-more:border-transparent contrast-more:hover:border-gray-900 contrast-more:dark:hover:border-gray-50"
>
{{- .Title -}}
</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] mt-5 mb-2 px-2 py-1.5 text-sm font-semibold text-gray-900 first:mt-0 dark:text-gray-100">
<span class="cursor-default">{{ $name }}</span>
</li>
{{ else }}
<li>{{ template "sidebar-item-link" dict "active" false "title" $name "link" (.URL | relLangURL) }}</li>
{{ end }}
{{- end -}}
{{- end -}} {{- end -}}
{{- define "sidebar-item-link" -}} {{- define "sidebar-item-link" -}}
{{- $external := strings.HasPrefix .link "http" -}} {{- $external := strings.HasPrefix .link "http" -}}
{{- $open := .open | default true -}}
<a <a
class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word] class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
{{- if .active }} {{- if .active }}
@ -144,21 +50,30 @@
href="{{ .link }}" href="{{ .link }}"
{{ if $external }}target="_blank" rel="noreferer"{{ end }} {{ if $external }}target="_blank" rel="noreferer"{{ end }}
> >
{{- .title -}} {{- .title | htmlUnescape | safeHTML -}}
{{- with .context }} {{- with .context }}
{{- if or .RegularPages .Sections }} {{- if or .RegularPages .Sections .section }}{{ partialCached "sidebar/collapsible-button" . }}{{ end -}}
<span class="hextra-sidebar-collapsible-button">
{{- template "sidebear-collapsible-button" -}}
</span>
{{- end }}
{{ end -}} {{ end -}}
</a> </a>
{{- end -}} {{- end -}}
{{- define "sidebar-separator" -}} {{- define "partials/sidebar/switches" -}}
<div class="mt-4 border-t py-4 dark:border-neutral-800 contrast-more:border-neutral-400 dark:contrast-more:border-neutral-400" /> {{- $context := .context -}}
{{- end -}} {{- $disableSidebar := .disableSidebar -}}
{{/* Hide theme switch when sidebar is disabled */}}
{{ $switchesClass := cond $disableSidebar "md:hidden" "" -}}
{{ $displayThemeToggle := (site.Params.theme.displayToggle | default true) -}}
{{- define "sidebear-collapsible-button" -}} {{ if or site.IsMultiLingual $displayThemeToggle }}
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" class="h-[18px] min-w-[18px] rounded-sm p-0.5 hover:bg-gray-800/5 dark:hover:bg-gray-100/5"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" class="origin-center transition-transform rtl:-rotate-180"></path></svg> <div class="{{ $switchesClass }} {{ with site.IsMultiLingual }}justify-end{{ end }} sticky bottom-0 bg-white dark:bg-dark mx-4 py-4 shadow-[0_-12px_16px_#fff] flex items-center gap-2 dark:border-neutral-800 dark:shadow-[0_-12px_16px_#111] contrast-more:border-neutral-400 contrast-more:shadow-none contrast-more:dark:shadow-none border-t" data-toggle-animation="show">
{{- with site.IsMultiLingual -}}
{{- partial "language-switch" (dict "context" $context "grow" true) -}}
{{- with $displayThemeToggle }}{{ partial "theme-toggle" (dict "hideLabel" true) }}{{ end -}}
{{- else -}}
{{- with $displayThemeToggle -}}
<div class="flex grow flex-col">{{ partial "theme-toggle" }}</div>
{{- end -}}
{{- end -}}
</div>
{{- end -}}
{{- end -}} {{- end -}}

View File

@ -0,0 +1,3 @@
<span class="hextra-sidebar-collapsible-button">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" class="h-[18px] min-w-[18px] rounded-sm p-0.5 hover:bg-gray-800/5 dark:hover:bg-gray-100/5"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" class="origin-center transition-transform rtl:-rotate-180"></path></svg>
</span>

View File

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

View File

@ -0,0 +1,15 @@
{{- $entry := . -}}
<div class="pt-1 ltr:pr-0 overflow-hidden transition-all ease-in-out duration-200">
<ul class='relative flex flex-col gap-1 before:absolute before:inset-y-1 before:w-px before:bg-gray-200 before:content-[""] ltr:ml-3 ltr:pl-3 ltr:before:left-0 rtl:mr-3 rtl:pr-3 rtl:before:right-0 dark:before:bg-neutral-800'>
{{- range $entry }}
{{- $shouldOpen := .open | default false }}
<li class="flex flex-col gap-1 {{ if $shouldOpen }}open{{ end }}">
{{- template "sidebar-item-link" (dict "context" . "active" false "title" .title "link" .link) -}}
{{- if .section -}}
{{- partial "sidebar/render-tree-branch-without-leaf" .section -}}
{{- end -}}
</li>
{{- end -}}
</ul>
</div>

View File

@ -0,0 +1,19 @@
{{- $context := .context -}}
{{- $page := .page -}}
{{- $entry := .entry -}}
{{- $pageLink := .page.RelPermalink -}}
<div class="pt-1 ltr:pr-0 overflow-hidden transition-all ease-in-out duration-200">
<ul class='relative flex flex-col gap-1 before:absolute before:inset-y-1 before:w-px before:bg-gray-200 before:content-[""] ltr:ml-3 ltr:pl-3 ltr:before:left-0 rtl:mr-3 rtl:pr-3 rtl:before:right-0 dark:before:bg-neutral-800'>
{{- range $entry }}
{{- $active := eq $pageLink .link -}}
{{- $shouldOpen := or (.open) (hasPrefix $pageLink .link) $active | default true }}
<li class="flex flex-col gap-1 {{ if $shouldOpen }}open{{ end }}">
{{- template "sidebar-item-link" (dict "context" . "active" $active "title" .title "link" .link) -}}
{{- if .section -}}
{{- partial "sidebar/render-tree-branch" (dict "context" $context "entry" .section "page" $page) -}}
{{- end -}}
</li>
{{- end -}}
</ul>
</div>

View File

@ -0,0 +1,18 @@
{{- $context := .context -}}
{{- $page := .page -}}
{{- $pageLink := .page.RelPermalink -}}
{{- range .tree -}}
{{- $active := eq $pageLink .link -}}
{{- $containsPage := hasPrefix $pageLink .link -}}
{{- $shouldOpen := or (.open) $containsPage $active | default false }}
<li class="{{ if $shouldOpen }}open{{ end }}">
{{- template "sidebar-item-link" (dict "context" . "active" $active "title" .title "link" .link) -}}
{{- if .section -}}
{{- if not $containsPage -}}
{{- partialCached "sidebar/render-tree-branch-without-leaf" .section .section -}}
{{- else -}}
{{- partial "sidebar/render-tree-branch" (dict "context" $context "entry" .section "page" $page) -}}
{{- end -}}
{{- end -}}
</li>
{{ end }}

View File

@ -0,0 +1,51 @@
{{ $context := . -}}
{{- $pages := union .RegularPages .Sections -}}
{{- $pages = where $pages "Params.sidebar.exclude" "!=" true -}}
{{- $data := slice -}}
{{- range $pages.ByWeight -}}
{{ $structure := (partial "sidebar/section-walk" .) | unmarshal -}}
{{ $data = $data | append $structure -}}
{{ end -}}
{{- define "partials/sidebar/section-walk" -}}
{{- with . -}}
{
"title": "{{ .LinkTitle | default .File.BaseFileName }}",
"link": "{{ .RelPermalink }}",
"toc": {{ partial "sidebar/section-page-toc" . }},
"open": {{ .Params.sidebar.open | default false }}
{{- if .IsSection }},
"section": [
{{ $pages := union .RegularPages .Sections -}}
{{ $pages = where $pages "Params.sidebar.exclude" "!=" true -}}
{{ range $index, $page := $pages.ByWeight -}}
{{ partial "sidebar/section-walk" . }}{{ if not (ge $index (sub (len $pages) 1)) }},{{ end -}}
{{ end -}}
]
{{ end -}}
}
{{- end }}
{{- end -}}
{{- define "partials/sidebar/section-page-toc" -}}
{{/* Get level 2 headings list used mainly for mobile navigation */}}
[
{{- with .Fragments.Headings -}}
{{/* Loop over level 1 headings */}}
{{- range . }}
{{- with .Headings }}
{{ $headings := . }}
{{- range $index, $heading := $headings }}
{{ $heading.Title | jsonify (dict "noHTMLEscape" true) }}
{{- if not (ge $index (sub (len $headings) 1)) }},{{ end -}}
{{ end -}}
{{- end -}}
{{ end -}}
{{- end -}}
]
{{- end -}}
{{ return ($data | jsonify (dict "noHTMLEscape" true)) }}

View File

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

View File

@ -0,0 +1,42 @@
{{/* Mermaid */}}
{{/* FIXME: need to investigate .Page.Store hasMermaid is set for homepage */}}
{{- if and (.Page.Store.Get "hasMermaid") (not .Page.IsHome) -}}
{{- $mermaidJS := resources.Get "lib/mermaid/mermaid.min.js" | fingerprint -}}
<script defer src="{{ $mermaidJS.RelPermalink }}" integrity="{{ $mermaidJS.Data.Integrity }}"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const theme = document.documentElement.classList.contains("dark") ? "dark" : "default";
mermaid.initialize({ startOnLoad: true, theme: theme });
});
</script>
{{- end -}}
{{/* KaTex */}}
{{- if .Page.Params.math -}}
{{- $katexCSS := resources.Get "lib/katex/katex.min.css" | fingerprint -}}
{{- $katexJS := resources.Get "lib/katex/katex.min.js" | fingerprint -}}
{{- $mhchemJS := resources.Get "lib/katex/mhchem.min.js" | fingerprint -}}
{{- $katexAutoRenderJS := resources.Get "lib/katex/auto-render.min.js" | fingerprint -}}
<link type="text/css" rel="stylesheet" href="{{ $katexCSS.RelPermalink }}" integrity="{{ $katexCSS.Data.Integrity }}" />
<script defer src="{{ $katexJS.RelPermalink }}" integrity="{{ $katexJS.Data.Integrity }}"></script>
<script defer src="{{ $katexAutoRenderJS.RelPermalink }}" integrity="{{ $katexAutoRenderJS.Data.Integrity }}"></script>
<script defer src="{{ $mhchemJS.RelPermalink }}" integrity="{{ $mhchemJS.Data.Integrity }}"></script>
{{ $katexFonts := resources.Match "lib/katex/fonts/*" }}
{{- range $katexFonts -}}
{{ .Publish }}
{{- end -}}
<script>
// TODO: make render options configurable
document.addEventListener("DOMContentLoaded", function () {
renderMathInElement(document.body, {
delimiters: [
{ left: "$$", right: "$$", display: true },
{ left: "$", right: "$", display: false },
{ left: "\\(", right: "\\)", display: false },
{ left: "\\[", right: "\\]", display: true },
],
throwOnError: false,
});
});
</script>
{{ end }}

View File

@ -3,6 +3,7 @@
{{- $toc := .Params.toc | default true -}} {{- $toc := .Params.toc | default true -}}
{{- $onThisPage := (T "onThisPage") | default "On this page"}} {{- $onThisPage := (T "onThisPage") | default "On this page"}}
{{- $editThisPage := (T "editThisPage") | default "Edit this page"}} {{- $editThisPage := (T "editThisPage") | default "Edit this page"}}
{{- $backToTop := (T "backToTop") | default "Scroll to top" -}}
<nav class="hextra-toc order-last hidden w-64 shrink-0 xl:block print:hidden px-4" aria-label="table of contents"> <nav class="hextra-toc order-last hidden w-64 shrink-0 xl:block print:hidden px-4" aria-label="table of contents">
{{- if $toc }} {{- if $toc }}
@ -25,10 +26,19 @@
<div class="{{ $borderClass }} sticky bottom-0 flex flex-col items-start gap-2 pb-8 dark:border-neutral-800 contrast-more:border-t contrast-more:border-neutral-400 contrast-more:shadow-none contrast-more:dark:border-neutral-400"> <div class="{{ $borderClass }} sticky bottom-0 flex flex-col items-start gap-2 pb-8 dark:border-neutral-800 contrast-more:border-t contrast-more:border-neutral-400 contrast-more:shadow-none contrast-more:dark:border-neutral-400">
{{- if site.Params.editURL.enable -}} {{- if site.Params.editURL.enable -}}
{{- $editURL := site.Params.editURL.base | default "" -}} {{- $editURL := site.Params.editURL.base | default "" -}}
{{- with .File -}}{{ $editURL = urls.JoinPath $editURL .Path }}{{- end -}} {{- with .File -}}{{ $editURL = urls.JoinPath $editURL (replace .Path "\\" "/") }}{{- end -}}
{{- with .Params.editURL -}}{{ $editURL = .Params.editURL }}{{- end -}} {{- with .Params.editURL -}}{{ $editURL = .Params.editURL }}{{- end -}}
<a class="text-xs font-medium text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100 contrast-more:text-gray-800 contrast-more:dark:text-gray-50" href="{{ $editURL }}" target="_blank" rel="noreferer">{{ $editThisPage }}</a> <a class="text-xs font-medium text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100 contrast-more:text-gray-800 contrast-more:dark:text-gray-50" href="{{ $editURL }}" target="_blank" rel="noreferer">{{ $editThisPage }}</a>
{{- end -}} {{- end -}}
{{/* Scroll To Top */}}
<button aria-hidden="true" id="backToTop" onClick="scrollUp();" class="transition-all transition duration-75 opacity-0 text-xs font-medium text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100 contrast-more:text-gray-800 contrast-more:dark:text-gray-50">
<span>
{{- $backToTop -}}
</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="inline ml-1 h-3.5 w-3.5 border rounded-full border-gray-500 hover:border-gray-900 dark:border-gray-400 dark:hover:border-gray-100 contrast-more:border-gray-800 contrast-more:dark:border-gray-50">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" />
</svg>
</button>
</div> </div>
</div> </div>
{{ end -}} {{ end -}}

View File

@ -1,5 +1,6 @@
{{/* Split page raw content into fragments */}} {{/* Split page raw content into fragments */}}
{{ $page := . }} {{ $page := .context }}
{{ $type := .type | default "content" }}
{{ $headingKeys := slice }} {{ $headingKeys := slice }}
{{ $headingTitles := slice }} {{ $headingTitles := slice }}
@ -22,24 +23,40 @@
{{ $len := len $headingKeys }} {{ $len := len $headingKeys }}
{{ $data := dict }} {{ $data := dict }}
{{ if eq $len 0 }} {{ if eq $type "content" }}
{{ $data = $data | merge (dict "" $page.Plain) }} {{/* Include full content of the page */}}
{{ else }} {{ if eq $len 0 }}
{{ range seq $len }} {{ $data = $data | merge (dict "" ($page.Plain | htmlUnescape | chomp)) }}
{{ $i := sub $len . }} {{ else }}
{{ $headingKey := index $headingKeys $i }} {{/* Split the raw content from bottom to top */}}
{{ $headingTitle := index $headingTitles $i }} {{ range seq $len }}
{{ $i := sub $len . }}
{{ $headingKey := index $headingKeys $i }}
{{ $headingTitle := index $headingTitles $i }}
{{ if eq $i 0 }} {{ if eq $i 0 }}
{{ $data = $data | merge (dict $headingKey ($content | markdownify | plainify)) }} {{ $data = $data | merge (dict $headingKey ($content | markdownify | plainify | htmlUnescape | chomp)) }}
{{ else }} {{ else }}
{{ $parts := split $content (printf "\n%s\n" $headingTitle) }} {{ $parts := split $content (printf "\n%s\n" $headingTitle) }}
{{ $lastPart := index $parts (sub (len $parts) 1) }} {{ $lastPart := index $parts (sub (len $parts) 1) }}
{{ $data = $data | merge (dict $headingKey ($lastPart | markdownify | plainify)) }} {{ $data = $data | merge (dict $headingKey ($lastPart | markdownify | plainify | htmlUnescape | chomp)) }}
{{ $content = strings.TrimSuffix $lastPart $content }} {{ $content = strings.TrimSuffix $lastPart $content }}
{{ $content = strings.TrimSuffix (printf "\n%s\n" $headingTitle) $content }} {{ $content = strings.TrimSuffix (printf "\n%s\n" $headingTitle) $content }}
{{ end }}
{{ end }} {{ end }}
{{ end }} {{ end }}
{{ else if (eq $type "heading" ) }}
{{/* Put heading keys with empty content to the data object */}}
{{ $data = dict "" "" }}
{{ range $headingKeys }}
{{ $data = $data | merge (dict . "") }}
{{ end }}
{{ else if (eq $type "title") }}
{{/* Use empty data object since title is included in search-data.json */}}
{{ $data = $data | merge (dict "" "") }}
{{ else if (eq $type "summary" ) }}
{{ $data = $data | merge (dict "" ($page.Summary | plainify | htmlUnescape | chomp)) }}
{{ end }} {{ end }}
{{ return $data }} {{ return $data }}

View File

@ -17,8 +17,8 @@
<div class="overflow-x-auto mt-6 flex rounded-lg border py-2 ltr:pr-4 rtl:pl-4 contrast-more:border-current contrast-more:dark:border-current {{ $class }}"> <div class="overflow-x-auto mt-6 flex rounded-lg border py-2 ltr:pr-4 rtl:pl-4 contrast-more:border-current contrast-more:dark:border-current {{ $class }}">
<div class="select-none text-xl ltr:pl-3 ltr:pr-2 rtl:pr-3 rtl:pl-2" style='font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";'>{{ $emoji }}</div> <div class="select-none text-xl ltr:pl-3 ltr:pr-2 rtl:pr-3 rtl:pl-2" style='font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";'>{{ $emoji }}</div>
<div class="w-full min-w-0 leading-7"> <div class="w-full min-w-0 leading-7">
<p class="mt-6 leading-7 first:mt-0"> <div class="mt-6 leading-7 first:mt-0">
{{ .Inner | markdownify }} {{ .Inner | markdownify }}
</p> </div>
</div> </div>
</div> </div>

View File

@ -3,7 +3,8 @@
"dev:theme": "hugo server --logLevel=debug --config=hugo.yaml,../dev.toml --environment=theme --source=exampleSite --themesDir=../.. --disableFastRender -D --port 1313", "dev:theme": "hugo server --logLevel=debug --config=hugo.yaml,../dev.toml --environment=theme --source=exampleSite --themesDir=../.. --disableFastRender -D --port 1313",
"dev": "hugo server --source=exampleSite --themesDir=../.. --disableFastRender -D --port 1313", "dev": "hugo server --source=exampleSite --themesDir=../.. --disableFastRender -D --port 1313",
"build:css": "npx postcss --config postcss.config.js --env production assets/css/styles.css -o assets/css/compiled/main.css", "build:css": "npx postcss --config postcss.config.js --env production assets/css/styles.css -o assets/css/compiled/main.css",
"build": "hugo --gc --minify --themesDir=../.. --source=exampleSite" "build": "hugo --gc --minify --themesDir=../.. --source=exampleSite",
"metrics": "hugo --themesDir=../.. --source=exampleSite --templateMetrics --templateMetricsHints"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e", "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",

View File

@ -3,10 +3,10 @@ const colors = require('tailwindcss/colors')
const makePrimaryColor = const makePrimaryColor =
l => l =>
({ opacityValue }) => { ({ opacityValue }) => {
if (opacityValue === undefined) { return (
return `hsl(var(--primary-hue) 100% ${l}%)` `hsl(var(--primary-hue) var(--primary-saturation) ${l}%` +
} (opacityValue ? ` / ${opacityValue})` : ')')
return `hsl(var(--primary-hue) 100% ${l}% / ${opacityValue})` )
} }
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */