Compare commits

...

55 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
Xin
3c4ede96df feat: add shortcodes to build Hextra home page (#90)
* feat: add feature grid and card shortcodes

* feat: add markdown content to hextra home

* chore: add button for hextra home

* chore: add heading and subtitle

* chore: finish moving hextra home to shortcodes

* chore: regenerate css

* chore: improve hextra home layout shortcodes

* chore: update css

* chore: decrease button size
2023-09-26 08:15:31 +01:00
Xin
01f7e3a425 docs: add showcase page (#86)
* docs: add showcase page

* chore: update compiled CSS

* chore: add showcase to navbar

* chore: reorder show case on navbar

* chore: update card image style

* chore: update showcase images

* chore: update showcase card image
2023-09-24 15:30:17 +01:00
da5a087891 fix: reverse order in blog list page (#87)
* feat: support reverse order in blog list page

* chore: update blog list order

---------

Co-authored-by: Xin <xin@imfing.com>
2023-09-24 14:54:38 +01:00
Xin
79883dc7cc fix: process static image url correctly 2023-09-24 11:03:34 +01:00
Xin
b283227046 feat: enhance image resolving (#82)
* feat: enhance image resolving

* chore: don't process image path that begins with relative link

* docs: add instruction for adding images

* chore: update docs

* chore: add filenames to images docs
2023-09-23 23:54:17 +01:00
Xin
0e9cf1a519 fix: add integrity attribute to styles css preload tag (#83) 2023-09-23 22:42:05 +01:00
Xin
fdc30c6cd5 fix: card image not displaying correctly (#77)
* fix: use relative URL for card images

* chore: update card image processing

* chore: add width and height for process image

* docs: update cards docs
2023-09-23 19:25:51 +01:00
Xin
3632294706 chore: update list page RSS template (#75)
* chore: update RSS feed template

* chore: use html instead of safeHTML
2023-09-22 23:41:53 +01:00
929578192b fix: htmlUnescape page description (#71)
- add htmlUnescape
2023-09-21 22:46:08 +01:00
Sid
c18d5def26 ci: upgrade actions/checkout from v3 to v4 (#73)
https://github.com/actions/checkout#checkout-v4
2023-09-21 21:42:15 +01:00
Xin
4e63aa4f14 feat: add google analytics support (#70)
* feat: support google analytics

* docs: add instruction for setting up ga
2023-09-21 08:03:35 +01:00
Xin
b51bfa3177 feat: support wide and full page modes (#69)
* feat: add page width config and partial

* feat: use page-width partial in layouts

* chore: revert changes in navbar and footer

* feat: customize footer width from site config

* chore: update styles

* docs: add page width

* feat: allow overriding navbar width

* fix: navbar width variable

* docs: add instruction for navbar and footer
2023-09-20 23:36:00 +01:00
c799160e86 feat: add native image processing to cards (#63)
* Add native image processing to cards

* feat: Image processing options, markdown subtitles

* fixing a type

* Public Domain Images

* image height fix

* Smaller Image

* Removed fullwidth param

* Smaller image from unsplash

* Replaced static image as well

* Update single.html

* Update list.html

* Update list.html

* Update single.html

* Update list.html

* Update single.html

* Update cards.md

* Update cards.md

---------

Co-authored-by: Xin <xin@imfing.com>
2023-09-19 00:04:48 +01:00
80 changed files with 1551 additions and 547 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

@ -34,7 +34,7 @@ jobs:
HUGO_VERSION: 0.117.0 HUGO_VERSION: 0.117.0
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Go - name: Setup Go

View File

@ -613,6 +613,9 @@ video {
.mb-4 { .mb-4 {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.mb-6 {
margin-bottom: 1.5rem;
}
.mb-8 { .mb-8 {
margin-bottom: 2rem; margin-bottom: 2rem;
} }
@ -694,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;
} }
@ -779,6 +788,9 @@ video {
.max-w-\[min\(calc\(100vw-2rem\)\2c calc\(100\%\+20rem\)\)\] { .max-w-\[min\(calc\(100vw-2rem\)\2c calc\(100\%\+20rem\)\)\] {
max-width: min(calc(100vw - 2rem),calc(100% + 20rem)); max-width: min(calc(100vw - 2rem),calc(100% + 20rem));
} }
.max-w-full {
max-width: 100%;
}
.max-w-none { .max-w-none {
max-width: none; max-width: none;
} }
@ -935,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));
@ -971,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;
@ -999,17 +1015,9 @@ video {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(254 252 232 / var(--tw-bg-opacity)); background-color: rgb(254 252 232 / var(--tw-bg-opacity));
} }
.bg-gradient-to-b {
background-image: linear-gradient(to bottom, var(--tw-gradient-stops));
}
.bg-gradient-to-r { .bg-gradient-to-r {
background-image: linear-gradient(to right, var(--tw-gradient-stops)); background-image: linear-gradient(to right, var(--tw-gradient-stops));
} }
.from-gray-800 {
--tw-gradient-from: #1f2937 var(--tw-gradient-from-position);
--tw-gradient-to: rgb(31 41 55 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-gray-900 { .from-gray-900 {
--tw-gradient-from: #111827 var(--tw-gradient-from-position); --tw-gradient-from: #111827 var(--tw-gradient-from-position);
--tw-gradient-to: rgb(17 24 39 / 0) var(--tw-gradient-to-position); --tw-gradient-to: rgb(17 24 39 / 0) var(--tw-gradient-to-position);
@ -1113,12 +1121,18 @@ video {
.pl-\[max\(env\(safe-area-inset-left\)\2c 1\.5rem\)\] { .pl-\[max\(env\(safe-area-inset-left\)\2c 1\.5rem\)\] {
padding-left: max(env(safe-area-inset-left),1.5rem); padding-left: max(env(safe-area-inset-left),1.5rem);
} }
.pr-2 {
padding-right: 0.5rem;
}
.pr-4 { .pr-4 {
padding-right: 1rem; padding-right: 1rem;
} }
.pr-\[calc\(env\(safe-area-inset-right\)-1\.5rem\)\] { .pr-\[calc\(env\(safe-area-inset-right\)-1\.5rem\)\] {
padding-right: calc(env(safe-area-inset-right) - 1.5rem); padding-right: calc(env(safe-area-inset-right) - 1.5rem);
} }
.pr-\[max\(env\(safe-area-inset-left\)\2c 1\.5rem\)\] {
padding-right: max(env(safe-area-inset-left),1.5rem);
}
.pr-\[max\(env\(safe-area-inset-right\)\2c 1\.5rem\)\] { .pr-\[max\(env\(safe-area-inset-right\)\2c 1\.5rem\)\] {
padding-right: max(env(safe-area-inset-right),1.5rem); padding-right: max(env(safe-area-inset-right),1.5rem);
} }
@ -1197,6 +1211,9 @@ video {
.leading-7 { .leading-7 {
line-height: 1.75rem; line-height: 1.75rem;
} }
.leading-none {
line-height: 1;
}
.leading-tight { .leading-tight {
line-height: 1.25; line-height: 1.25;
} }
@ -1244,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;
@ -1363,6 +1380,12 @@ video {
.duration-200 { .duration-200 {
transition-duration: 200ms; transition-duration: 200ms;
} }
.duration-75 {
transition-duration: 75ms;
}
.ease-in {
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
}
.ease-in-out { .ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
} }
@ -1416,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));
} }
@ -1482,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;
@ -1522,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;
@ -1534,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) {
@ -2141,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;
@ -2151,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 {
@ -2175,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;
@ -2185,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));
} }
@ -2238,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));
@ -2330,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 {
@ -2423,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 {
@ -2562,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;
@ -2650,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));
@ -2659,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;
@ -2683,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;
@ -2720,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);
@ -2762,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;
@ -2908,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));
@ -2920,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 {
@ -2983,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));
@ -3028,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);
@ -3089,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;
@ -3142,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));
@ -3174,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;
@ -3209,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;
@ -3222,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) {
@ -3242,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) {
@ -3344,11 +3390,6 @@ body {
align-items: flex-start; align-items: flex-start;
} }
.sm\:px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.sm\:text-xl { .sm\:text-xl {
font-size: 1.25rem; font-size: 1.25rem;
} }

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

@ -18,6 +18,9 @@
target = '$1' target = '$1'
[module] [module]
[[module.mounts]]
source = "assets"
target = "assets"
[[module.mounts]] [[module.mounts]]
source = "hugo_stats.json" source = "hugo_stats.json"
target = "assets/watching/hugo_stats.json" target = "assets/watching/hugo_stats.json"

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

@ -2,3 +2,75 @@
title: Hextra Theme title: Hextra Theme
layout: hextra-home layout: hextra-home
--- ---
{{< hextra/hero-badge >}}
<div class="w-2 h-2 rounded-full bg-primary-400"></div>
<span>Free, open source</span>
{{< icon name="arrow-circle-right" attributes="height=14" >}}
{{< /hextra/hero-badge >}}
<div class="mt-6 mb-6">
{{< hextra/hero-headline >}}
Build modern websites&nbsp;<br class="sm:block hidden" />with Markdown and Hugo
{{< /hextra/hero-headline >}}
</div>
<div class="mb-12">
{{< hextra/hero-subtitle >}}
Fast, batteries-included Hugo theme&nbsp;<br class="sm:block hidden" />for creating beautiful static websites
{{< /hextra/hero-subtitle >}}
</div>
<div class="mb-6">
{{< hextra/hero-button text="Get Started" link="docs" >}}
</div>
<div class="mt-6"></div>
{{< hextra/feature-grid >}}
{{< hextra/feature-card
title="Fast and Full-featured"
subtitle="Simple and easy to use, yet powerful and feature-rich."
class="aspect-auto md:aspect-[1.1/1] max-md:min-h-[340px]"
image="images/hextra-doc.webp"
imageClass="top-[40%] left-[24px] w-[180%] sm:w-[110%] dark:opacity-80"
style="background: radial-gradient(ellipse at 50% 80%,rgba(194,97,254,0.15),hsla(0,0%,100%,0));"
>}}
{{< hextra/feature-card
title="Markdown is All You Need"
subtitle="Compose with just Markdown. Enrich with Shortcode components."
class="aspect-auto md:aspect-[1.1/1] max-lg:min-h-[340px]"
image="images/hextra-markdown.webp"
imageClass="top-[40%] left-[36px] w-[180%] sm:w-[110%] dark:opacity-80"
style="background: radial-gradient(ellipse at 50% 80%,rgba(142,53,74,0.15),hsla(0,0%,100%,0));"
>}}
{{< hextra/feature-card
title="Full Text Search"
subtitle="Built-in full text search with FlexSearch, no extra setup required."
class="aspect-auto md:aspect-[1.1/1] max-md:min-h-[340px]"
image="images/hextra-search.webp"
imageClass="top-[40%] left-[36px] w-[110%] sm:w-[110%] dark:opacity-80"
style="background: radial-gradient(ellipse at 50% 80%,rgba(221,210,59,0.15),hsla(0,0%,100%,0));"
>}}
{{< hextra/feature-card
title="Lightweight as a Feather"
subtitle="No dependency or Node.js is needed to use Hextra. Powered by Hugo, one of *the fastest* static site generators, building your site in just seconds with a single binary."
>}}
{{< hextra/feature-card
title="Reponsive with Dark Mode Included"
subtitle="Looks great on different screen sizes. Built-in dark mode support, with auto-switching based on user's system preference."
>}}
{{< hextra/feature-card
title="Build and Host for Free"
subtitle="Build with GitHub Actions, and host for free on GitHub Pages. Alternatively it can be hosted on any static hosting service."
>}}
{{< hextra/feature-card
title="Multi-Language Made Easy"
subtitle="Create multi-language pages by just adding locales suffix to the Markdown file. Adding i18n support to your site is intuitive."
>}}
{{< hextra/feature-card
title="And Much More..."
icon="sparkles"
subtitle="Syntax highlighting / Table of contents / SEO / RSS / LaTeX / Mermaid / Customizable / and more..."
>}}
{{< /hextra/feature-grid >}}

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-->
@ -180,3 +180,80 @@ 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
The width of the page can be customized by the `params.page.width` parameter in the config file:
```yaml {filename="hugo.yaml"}
params:
page:
# full (100%), wide (90rem), normal (1280px)
width: wide
```
There are three available options: `full`, `wide`, and `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.
### 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
To enable Google Analytics, set the `googleAnalytics` parameter in the config file:
```yaml {filename="hugo.yaml"}
googleAnalytics: G-XXXXXXXXXX
```

View File

@ -63,3 +63,63 @@ weight: 2
## Configure Content Directory ## Configure Content Directory
If we need to use a different directory for our content, we can do so by setting the [`contentDir`](https://gohugo.io/getting-started/configuration/#contentdir) parameter in our site configuration file. If we need to use a different directory for our content, we can do so by setting the [`contentDir`](https://gohugo.io/getting-started/configuration/#contentdir) parameter in our site configuration file.
## Add Images
To add images, the easiest way is to put the image files in the same directory as the Markdown file.
For example, add an image file `image.png` alongside the `my-page.md` file:
{{< filetree/container >}}
{{< filetree/folder name="content" >}}
{{< filetree/folder name="docs" >}}
{{< filetree/file name="my-page.md" >}}
{{< filetree/file name="image.png" >}}
{{< /filetree/folder >}}
{{< /filetree/folder >}}
{{< /filetree/container >}}
Then, we can use the following Markdown syntax to add the image to the content:
```markdown {filename="content/docs/my-page.md"}
![](image.png)
```
We can also utilize the [page bundles][page-bundles] feature of Hugo to organize the image files together with the Markdown file. To achieve that, turn the `my-page.md` file into a directory `my-page` and put the content into a file named `index.md`, and put the image files inside the `my-page` directory:
{{< filetree/container >}}
{{< filetree/folder name="content" >}}
{{< filetree/folder name="docs" >}}
{{< filetree/folder name="my-page" >}}
{{< filetree/file name="index.md" >}}
{{< filetree/file name="image.png" >}}
{{< /filetree/folder >}}
{{< /filetree/folder >}}
{{< /filetree/folder >}}
{{< /filetree/container >}}
```markdown {filename="content/docs/my-page/index.md"}
![](image.png)
```
Alternatively, we can also put the image files in the `static` directory, which will make the images available for all pages:
{{< filetree/container >}}
{{< filetree/folder name="static" >}}
{{< filetree/folder name="images" >}}
{{< filetree/file name="image.png" >}}
{{< /filetree/folder >}}
{{< /filetree/folder >}}
{{< filetree/folder name="content" >}}
{{< filetree/folder name="docs" >}}
{{< filetree/file name="my-page.md" >}}
{{< /filetree/folder >}}
{{< /filetree/folder >}}
{{< /filetree/container >}}
Note that the image path begins with a slash `/` and is relative to the static directory:
```markdown {filename="content/docs/my-page.md"}
![](/images/image.png)
```
[page-bundles]: https://gohugo.io/content-management/page-bundles/#leaf-bundles

View File

@ -12,9 +12,10 @@ linkTitle: Cards
{{< cards >}} {{< cards >}}
{{< card link="/" title="Image Card" image="https://source.unsplash.com/featured/800x600?landscape" subtitle="Unsplash Landscape" >}} {{< card link="/" title="Image Card" image="https://source.unsplash.com/featured/800x600?landscape" subtitle="Unsplash Landscape" >}}
{{< card link="/" title="Local Image" image="/images/card-image-unprocessed.jpg" subtitle="Raw image under static directory." >}}
{{< card link="/" title="Local Image" image="images/space.jpg" subtitle="Image under assets directory, processed by Hugo." method="Resize" options="600x q80 webp" >}}
{{< /cards >}} {{< /cards >}}
## Usage ## Usage
``` ```
@ -27,5 +28,37 @@ linkTitle: Cards
``` ```
{{</* cards */>}} {{</* cards */>}}
{{</* card link="/" title="Image Card" image="https://source.unsplash.com/featured/800x600?landscape" subtitle="Unsplash Landscape" */>}} {{</* card link="/" title="Image Card" image="https://source.unsplash.com/featured/800x600?landscape" subtitle="Unsplash Landscape" */>}}
{{</* card link="/" title="Local Image" image="/images/card-image-unprocessed.jpg" subtitle="Raw image under static directory." */>}}
{{</* card link="/" title="Local Image" image="images/space.jpg" subtitle="Image under assets directory, processed by Hugo." method="Resize" options="600x q80 webp" */>}}
{{</* /cards */>}} {{</* /cards */>}}
``` ```
## Card Parameters
| Parameter | Description |
|----------- |---------------------------------------|
| `link` | URL (internal or external). |
| `title` | Title heading for the card. |
| `subtitle` | Subtitle heading (supports Markdown). |
| `icon` | Name of the icon. |
## Image Card
Additionally, the card supports adding image and processing through these parameters:
| Parameter | Description |
|----------- |---------------------------------------------|
| `image` | Specifies the image URL for the card. |
| `method` | Sets Hugo's image processing method. |
| `options` | Configures Hugo's image processing options. |
Card supports three kinds of images:
1. Remote image: the full URL in the `image` parameter.
2. Static image: use the relative path in Hugo's `static/` directory.
3. Processed image: use the relative path in Hugo's `assets/` directory.
Hextra auto-detects if image processing is needed during build and applies the `options` parameter or default settings (Resize, 800x, Quality 80, WebP Format).
It currently supports these `method`: `Resize`, `Fit`, `Fill` and `Crop`.
For more on Hugo's built in image processing commands, methods, and options see their [Image Processing Documentation](https://gohugo.io/content-management/image-processing/).

View File

@ -0,0 +1,18 @@
---
title: Showcase
description: "Open source projects powered by Hextra."
toc: false
layout: wide
---
<div class="mt-4"></div>
<p class="mb-12 text-center text-lg text-gray-500 dark:text-gray-400">
Open source projects powered by Hextra.
</p>
{{< cards >}}
{{< card link="https://getporter.org/" title="Porter" image="https://repository-images.githubusercontent.com/155893691/aa249c80-fcf3-11ea-93b0-30079e8d7de4" imageStyle="object-fit:cover; aspect-ratio:16/9;" >}}
{{< card link="https://lutheranconfessions.org/" title="LutheranConfessions" image="https://github.com/imfing/hextra/assets/5097752/ad6625e4-88cd-4cad-b102-5399997d0359" imageStyle="object-fit:cover; aspect-ratio:16/9;" >}}
{{< card link="/" title="Hextra Starter Template" image="https://user-images.githubusercontent.com/5097752/263551418-c403b9a9-a76c-47a6-8466-513d772ef0b7.jpg" imageStyle="object-fit:cover; aspect-ratio:16/9;" >}}
{{< /cards >}}

View File

@ -7,6 +7,8 @@ enableGitInfo: true
# enableEmoji: false # enableEmoji: false
hasCJKLanguage: true hasCJKLanguage: true
# googleAnalytics: G-XXXXXXXXXX
outputs: outputs:
home: [HTML] home: [HTML]
page: [HTML] page: [HTML]
@ -24,13 +26,14 @@ languages:
title: "Hextra テーマ" title: "Hextra テーマ"
zh-cn: zh-cn:
languageName: 简体中文 languageName: 简体中文
languageCode: zh-CN
weight: 3 weight: 3
title: Hextra title: Hextra
module: module:
hugoVersion: hugoVersion:
extended: true extended: true
min: "0.111.0" min: "0.112.0"
workspace: hugo.work workspace: hugo.work
imports: imports:
@ -51,20 +54,24 @@ menu:
name: Documentation name: Documentation
pageRef: /docs pageRef: /docs
weight: 1 weight: 1
- identifier: showcase
name: Showcase
pageRef: /showcase
weight: 2
- identifier: blog - identifier: blog
name: Blog name: Blog
pageRef: /blog pageRef: /blog
weight: 2 weight: 3
- identifier: about - identifier: about
name: About name: About
pageRef: /about pageRef: /about
weight: 3
- name: Search
weight: 4 weight: 4
- name: Search
weight: 5
params: params:
type: search type: search
- name: GitHub - name: GitHub
weight: 5 weight: 6
url: "https://github.com/imfing/hextra" url: "https://github.com/imfing/hextra"
params: params:
icon: github icon: github
@ -93,20 +100,55 @@ params:
logo: logo:
path: images/logo.svg path: images/logo.svg
dark: images/logo-dark.svg dark: images/logo-dark.svg
# link: /
# width: 40 # width: 40
# height: 20 # height: 20
# link: /
width: wide
page:
# full (100%), wide (90rem), normal (1280px)
width: normal
theme:
# light | dark | system
default: system
displayToggle: true
footer: footer:
displayCopyright: true displayCopyright: true
displayPoweredBy: true displayPoweredBy: true
width: normal
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

@ -40,7 +40,6 @@
"path", "path",
"pre", "pre",
"script", "script",
"section",
"span", "span",
"strong", "strong",
"style", "style",
@ -94,7 +93,6 @@
"bg-black/[.05]", "bg-black/[.05]",
"bg-blue-100", "bg-blue-100",
"bg-clip-text", "bg-clip-text",
"bg-gradient-to-b",
"bg-gradient-to-r", "bg-gradient-to-r",
"bg-gray-100", "bg-gray-100",
"bg-neutral-50", "bg-neutral-50",
@ -114,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",
@ -129,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",
@ -154,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",
@ -173,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",
@ -190,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",
@ -228,6 +231,8 @@
"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-out", "ease-in-out",
"filename", "filename",
"first:mt-0", "first:mt-0",
@ -246,7 +251,6 @@
"footnote-backref", "footnote-backref",
"footnote-ref", "footnote-ref",
"footnotes", "footnotes",
"from-gray-800",
"from-gray-900", "from-gray-900",
"gap-1", "gap-1",
"gap-2", "gap-2",
@ -266,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",
@ -274,6 +279,7 @@
"hamburger-menu", "hamburger-menu",
"hextra-card", "hextra-card",
"hextra-cards", "hextra-cards",
"hextra-feature-card",
"hextra-filetree", "hextra-filetree",
"hextra-filetree-folder", "hextra-filetree-folder",
"hextra-footer", "hextra-footer",
@ -290,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",
@ -320,6 +327,7 @@
"leading-5", "leading-5",
"leading-6", "leading-6",
"leading-7", "leading-7",
"leading-none",
"leading-tight", "leading-tight",
"left-[24px]", "left-[24px]",
"left-[36px]", "left-[36px]",
@ -367,6 +375,7 @@
"mb-16", "mb-16",
"mb-2", "mb-2",
"mb-4", "mb-4",
"mb-6",
"mb-8", "mb-8",
"md:aspect-[1.1/1]", "md:aspect-[1.1/1]",
"md:h-[calc(100vh-var(--navbar-height)-var(--menu-height))]", "md:h-[calc(100vh-var(--navbar-height)-var(--menu-height))]",
@ -439,8 +448,10 @@
"pl-[max(env(safe-area-inset-left),1.5rem)]", "pl-[max(env(safe-area-inset-left),1.5rem)]",
"placeholder:text-gray-500", "placeholder:text-gray-500",
"pointer-events-none", "pointer-events-none",
"pr-2",
"pr-4", "pr-4",
"pr-[calc(env(safe-area-inset-right)-1.5rem)]", "pr-[calc(env(safe-area-inset-right)-1.5rem)]",
"pr-[max(env(safe-area-inset-left),1.5rem)]",
"pr-[max(env(safe-area-inset-right),1.5rem)]", "pr-[max(env(safe-area-inset-right),1.5rem)]",
"print:bg-transparent", "print:bg-transparent",
"print:hidden", "print:hidden",
@ -512,7 +523,6 @@
"sm:flex", "sm:flex",
"sm:grid-cols-2", "sm:grid-cols-2",
"sm:items-start", "sm:items-start",
"sm:px-4",
"sm:text-xl", "sm:text-xl",
"sm:w-[110%]", "sm:w-[110%]",
"sr-only", "sr-only",

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

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,8 +1,27 @@
{{- if .Title -}} {{- $alt := .PlainText | safeHTML -}}
{{- $lazyLoading := .Page.Site.Params.enableImagelazyLoading | default true -}}
{{- $dest := .Destination -}}
{{- $isRemote := not (urls.Parse $dest).Scheme -}}
{{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}}
{{- $startsWithSlash := hasPrefix $dest "/" -}}
{{- $startsWithRelative := hasPrefix $dest "../" -}}
{{- if and $dest $isRemote -}}
{{- if $startsWithSlash -}}
{{/* Images under static directory */}}
{{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}}
{{- else if and $isPage (not $startsWithRelative) -}}
{{/* Images that are sibling to the individual page file */}}
{{ $dest = (printf "../%s" $dest) }}
{{- end -}}
{{- end -}}
{{- with .Title -}}
<figure> <figure>
<img src="{{ .Destination | safeURL }}" title="{{ .Title }}" alt="{{ .PlainText | safeHTML }}" loading="lazy" /> <img src="{{ $dest | safeURL }}" title="{{ . }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} />
<figcaption>{{ .Title }}</figcaption> <figcaption>{{ . }}</figcaption>
</figure> </figure>
{{- else -}} {{- else -}}
<img src="{{ .Destination | safeURL }}" alt="{{ .PlainText | safeHTML }}" loading="lazy" /> <img src="{{ $dest | safeURL }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} />
{{- end -}} {{- end -}}

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

@ -1,5 +1,5 @@
{{ define "main" }} {{ define "main" }}
<div class="mx-auto flex max-w-screen-xl"> <div class='mx-auto flex {{ partial "utils/page-width" . }}'>
{{ partial "sidebar.html" (dict "context" .) }} {{ partial "sidebar.html" (dict "context" .) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="w-full break-words flex min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-center pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <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)]">
@ -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

@ -0,0 +1,43 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ .Site.Title }} {{ .Title }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
<generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
<language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
<managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{ with .OutputFormats.Get "RSS" }}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{ end }}
{{ if not $.Section }}
{{ $sections := .Site.Params.rss.sections | default (slice "blog") }}
{{ .Scratch.Set "rssPages" (first 50 (where $.Site.RegularPages "Type" "in" $sections )) }}
{{ else }}
{{ if $.Parent.IsHome }}
{{ .Scratch.Set "rssPages" (first 50 (where $.Site.RegularPages "Type" $.Section )) }}
{{ else }}
{{ .Scratch.Set "rssPages" (first 50 $.Pages) }}
{{ end }}
{{ end }}
{{ range (.Scratch.Get "rssPages") }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
<guid>{{ .Permalink }}</guid>
<description>
{{ $img := (.Resources.ByType "image").GetMatch "*featured*" }}
{{ with $img }}
{{ $img := .Resize "640x" }}
{{ printf "<![CDATA[<img src=\"%s\" width=\"%d\" height=\"%d\"/>]]>" $img.Permalink $img.Width $img.Height | safeHTML }}
{{ end }}
{{ .Content | html }}
</description>
</item>
{{ end }}
</channel>
</rss>

View File

@ -1,5 +1,5 @@
{{ define "main" }} {{ define "main" }}
<div class="mx-auto flex max-w-screen-xl"> <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) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="w-full break-words flex min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-center pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <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)]">
@ -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

@ -0,0 +1,12 @@
{{ define "main" }}
<div class="mx-auto flex max-w-[90rem]">
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" false) }}
<article class="w-full break-words min-h-[calc(100vh-var(--navbar-height))] min-w-0 pt-4 pb-8 pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-left),1.5rem)]">
<br class="mt-1.5 text-sm" />
<h1 class="text-center mt-2 text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100">{{ .Title }}</h1>
<div class="content">
{{ .Content }}
</div>
</article>
</div>
{{ end }}

View File

@ -1,17 +1,18 @@
{{ define "main" }} {{ define "main" }}
<div class="mx-auto flex max-w-screen-xl"> {{- $readMore := (T "readMore") | default "Read more →" -}}
<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)]">
<main class="w-full min-w-0 max-w-6xl px-6 pt-4 md:px-12"> <main class="w-full min-w-0 max-w-6xl px-6 pt-4 md:px-12">
<br class="mt-1.5 text-sm" /> <br class="mt-1.5 text-sm" />
<h1 class="text-center mt-2 text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100">{{ .Title }}</h1> <h1 class="text-center mt-2 text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100">{{ .Title }}</h1>
<div class="content">{{ .Content }}</div> <div class="content">{{ .Content }}</div>
{{ range .Pages.ByDate }} {{ range .Pages.ByDate.Reverse }}
<div class="mb-10"> <div class="mb-10">
<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

@ -1,5 +1,5 @@
{{ define "main" }} {{ define "main" }}
<div class="mx-auto flex max-w-screen-xl"> <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) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="w-full break-words flex min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-center pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <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)]">
@ -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

@ -1,5 +1,5 @@
{{ define "main" }} {{ define "main" }}
<div class="mx-auto flex max-w-screen-xl"> <div class='mx-auto flex {{ partial "utils/page-width" . }}'>
{{ partial "sidebar.html" (dict "context" .) }} {{ partial "sidebar.html" (dict "context" .) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="w-full break-words flex min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-center pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <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)]">
@ -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

@ -1,5 +1,5 @@
{{ define "main" }} {{ define "main" }}
<div class="mx-auto flex max-w-screen-xl"> <div class='mx-auto flex {{ partial "utils/page-width" . }}'>
{{ partial "sidebar.html" (dict "context" .) }} {{ partial "sidebar.html" (dict "context" .) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="w-full break-words flex min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-center pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <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,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

@ -1,118 +1,10 @@
{{ define "main" }} {{ define "main" }}
<div class="mx-auto flex max-w-screen-xl"> <div class='mx-auto flex {{ partial "utils/page-width" . }}'>
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true) }}
<div class="w-full break-words flex flex-col min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-start pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <div class="w-full break-words min-h-[calc(100vh-var(--navbar-height))] min-w-0 pb-8 pt-8 md:pt-12 pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-left),1.5rem)]">
<section class="flex max-w-[90rem] flex-col items-start gap-2 px-6 sm:px-4 pt-8 md:pt-12"> <div class="flex flex-col items-start">
<a {{ .Content }}
href="https://github.com/imfing/hextra" </div>
target="_blank"
rel="noreferrer"
class="inline-flex items-center rounded-full gap-2 px-3 py-1 text-xs text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-neutral-800 dark:border-neutral-800 border hover:border-gray-400 dark:hover:text-gray-50 dark:hover:border-gray-600"
>
<div class="w-2 h-2 rounded-full bg-primary-400"></div>
<span>Free, open source</span>
{{- partial "utils/icon" (dict "name" "arrow-circle-right" "attributes" "height=14") -}}
</a>
<h1
class="text-4xl font-bold leading-tight tracking-tighter md:text-5xl mt-6 bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600 dark:from-gray-100 dark:to-gray-400"
>
Build modern websites&nbsp;<br class="sm:block hidden" />
with Markdown and Hugo
</h1>
<p class="mt-4 text-xl text-gray-600 dark:text-gray-400 sm:text-xl">
Fast, batteries-included Hugo theme <br class="sm:block hidden" />
for creating beautiful static websites.
</p>
<div class="mt-8">
{{- $docsURL := "docs" | relURL -}}
<a
href="{{ $docsURL }}"
class="font-medium cursor-pointer px-6 py-3 text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-4 focus:ring-primary-300 rounded-full text-center mr-2 mb-2 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
>
Get Started
</a>
</div>
</section>
<section class="flex max-w-[90rem] flex-col items-start gap-2 px-6 sm:px-4 pt-8 pb-8">
<h2 class="mt-12 text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-b from-gray-800 to-gray-600 dark:from-gray-100 dark:to-gray-400">What's in Hextra?</h2>
<div class="mt-6 grid sm:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-4 w-full">
{{ template "hextra-feature-card" (dict
"title" "Fast and Full-featured"
"subtitle" "Simple and easy to use, yet powerful and feature-rich. "
"class" "aspect-auto md:aspect-[1.1/1] max-md:min-h-[340px]"
"image" "images/hextra-doc.webp"
"imageClass" "top-[40%] left-[24px] w-[180%] sm:w-[110%] dark:opacity-80"
"style" "background: radial-gradient(ellipse at 50% 80%,rgba(194,97,254,0.15),hsla(0,0%,100%,0));"
)
}}
{{ template "hextra-feature-card" (dict
"title" "Markdown is All You Need"
"subtitle" "Compose with just Markdown. Enrich with Shortcode components."
"class" "aspect-auto md:aspect-[1.1/1] max-lg:min-h-[340px]"
"image" "images/hextra-markdown.webp"
"imageClass" "top-[40%] left-[36px] w-[180%] sm:w-[110%] dark:opacity-80"
"style" "background: radial-gradient(ellipse at 50% 80%,rgba(142,53,74,0.15),hsla(0,0%,100%,0));"
)
}}
{{ template "hextra-feature-card" (dict
"title" "Full Text Search"
"subtitle" "Built-in full text search with FlexSearch, no extra setup required."
"class" "aspect-auto md:aspect-[1.1/1] max-md:min-h-[340px]"
"image" "images/hextra-search.webp"
"imageClass" "top-[40%] left-[36px] w-[110%] sm:w-[110%] dark:opacity-80"
"style" "background: radial-gradient(ellipse at 50% 80%,rgba(221,210,59,0.15),hsla(0,0%,100%,0));"
)
}}
{{ template "hextra-feature-card" (dict
"title" "Lightweight as a Feather"
"subtitle" "No dependency or Node.js is needed to use Hextra. Powered by Hugo, one of *the fastest* static site generators, building your site in just seconds with a single binary."
)
}}
{{ template "hextra-feature-card" (dict
"title" "Reponsive with Dark Mode Included"
"subtitle" "Looks great on different screen sizes. Built-in dark mode support, with auto-switching based on user's system preference."
)
}}
{{ template "hextra-feature-card" (dict
"title" "Build and Host for Free"
"subtitle" "Build with GitHub Actions, and host for free on GitHub Pages. Alternatively it can be hosted on any static hosting service."
)
}}
{{ template "hextra-feature-card" (dict
"title" "Multi-Language Made Easy"
"subtitle" "Create multi-language pages by just adding locales suffix to the Markdown file. Adding i18n support to your site is intuitive."
)
}}
{{ template "hextra-feature-card" (dict
"title" "And Much More..."
"subtitle" "Syntax highlighting / Table of contents / SEO / RSS / LaTeX / Mermaid / Customizable / and more..."
)
}}
</div>
</section>
</div> </div>
</div> </div>
{{ end }} {{ end }}
{{- define "hextra-feature-card" -}}
{{- $title := .title -}}
{{- $subtitle := .subtitle -}}
{{- $class := .class -}}
{{- $image := .image -}}
{{- $imageClass := .imageClass -}}
{{- $style := .style -}}
<div
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
class="{{ $class }} relative overflow-hidden rounded-3xl border border-gray-200 hover:border-gray-300 dark:border-neutral-800 dark:hover:border-neutral-700 before:pointer-events-none before:absolute before:inset-0 before:bg-glass-gradient"
>
<div class="relative w-full p-6">
<h3 class="text-2xl font-medium leading-6 mb-2">{{ $title }}</h3>
<p class="text-gray-500 text-sm leading-6">{{ $subtitle | markdownify }}</p>
</div>
{{- with $image -}}
<img src="{{ . }}" class="absolute max-w-none {{ $imageClass }}" alt="{{ $title }}" />
{{- end -}}
</div>
{{- end -}}

View File

@ -1,5 +1,5 @@
{{ define "main" }} {{ define "main" }}
<div class="mx-auto flex max-w-[90rem]"> <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) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="w-full break-words flex min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-center pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <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)]">

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,22 +1,34 @@
{{- $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." -}}
{{- $footerWidth := "max-w-screen-xl" -}}
{{- with .Site.Params.footer.width -}}
{{ if eq . "wide" -}}
{{ $footerWidth = "max-w-[90rem]" -}}
{{ else if eq . "full" -}}
{{ $footerWidth = "max-w-full" -}}
{{ 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 max-w-screen-xl gap-2 py-2 px-4"> <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="mx-auto flex max-w-screen-xl 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

@ -0,0 +1,13 @@
{{ with .Site.GoogleAnalytics }}
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ . }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "{{ . }}");
</script>
{{ end }}

View File

@ -7,7 +7,7 @@
{{- if hugo.IsProduction }} {{- if hugo.IsProduction }}
{{- $styles = $styles | minify | fingerprint }} {{- $styles = $styles | minify | fingerprint }}
<link rel="preload" href="{{ $styles.RelPermalink }}" as="style" /> <link rel="preload" href="{{ $styles.RelPermalink }}" as="style" integrity="{{ $styles.Data.Integrity }}" />
<link href="{{ $styles.RelPermalink }}" rel="stylesheet" integrity="{{ $styles.Data.Integrity }}" /> <link href="{{ $styles.RelPermalink }}" rel="stylesheet" integrity="{{ $styles.Data.Integrity }}" />
{{- else }} {{- else }}
<link href="{{ $styles.RelPermalink }}" rel="stylesheet" /> <link href="{{ $styles.RelPermalink }}" rel="stylesheet" />

View File

@ -16,19 +16,42 @@
{{ end -}} {{ end -}}
</title> </title>
<meta name="description" content="{{ partial "utils/page-description.html" . }}" /> <meta name="description" content="{{ partial "utils/page-description.html" . }}" />
{{ partial "opengraph.html" . }} {{ partial "opengraph.html" . }}
{{ template "_internal/schema.html" . -}} {{ template "_internal/schema.html" . -}}
{{ template "_internal/twitter_cards.html" . -}} {{ template "_internal/twitter_cards.html" . -}}
{{ partialCached "head-css.html" . }} {{ partialCached "head-css.html" . }}
<!-- Google Analytics -->
{{- if and .Site.GoogleAnalytics (eq hugo.Environment "production") }}
<link rel="preconnect" href="https://www.googletagmanager.com" crossorigin />
{{ partial "google-analytics.html" . }}
{{- end }}
<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

@ -4,11 +4,19 @@
{{- $logoHeight := .Site.Params.navbar.logo.height | default "20" -}} {{- $logoHeight := .Site.Params.navbar.logo.height | default "20" -}}
{{- $logoDarkPath := .Site.Params.navbar.logo.dark | default $logoPath -}} {{- $logoDarkPath := .Site.Params.navbar.logo.dark | default $logoPath -}}
{{- $navWidth := "max-w-[90rem]" -}}
{{- with .Site.Params.navbar.width -}}
{{ if eq . "normal" -}}
{{ $navWidth = "max-w-screen-xl" -}}
{{ else if eq . "full" -}}
{{ $navWidth = "max-w-full" -}}
{{ end -}}
{{- end -}}
<div class="nav-container sticky top-0 z-20 w-full bg-transparent print:hidden"> <div class="nav-container sticky top-0 z-20 w-full bg-transparent print:hidden">
<div class="nav-container-blur pointer-events-none absolute z-[-1] h-full w-full bg-white dark:bg-dark shadow-[0_2px_4px_rgba(0,0,0,.02),0_1px_0_rgba(0,0,0,.06)] contrast-more:shadow-[0_0_0_1px_#000] dark:shadow-[0_-1px_0_rgba(255,255,255,.1)_inset] contrast-more:dark:shadow-[0_0_0_1px_#fff]"></div> <div class="nav-container-blur pointer-events-none absolute z-[-1] h-full w-full bg-white dark:bg-dark shadow-[0_2px_4px_rgba(0,0,0,.02),0_1px_0_rgba(0,0,0,.06)] contrast-more:shadow-[0_0_0_1px_#000] dark:shadow-[0_-1px_0_rgba(255,255,255,.1)_inset] contrast-more:dark:shadow-[0_0_0_1px_#fff]"></div>
<nav class="mx-auto flex items-center justify-end gap-2 h-16 px-6 max-w-[90rem]"> <nav class="mx-auto flex items-center justify-end gap-2 h-16 px-6 {{ $navWidth }}">
<a class="flex items-center hover:opacity-75 ltr:mr-auto rtl:ml-auto" href="{{ $logoLink }}"> <a class="flex items-center hover:opacity-75 ltr:mr-auto rtl:ml-auto" href="{{ $logoLink }}">
{{- if (.Site.Params.navbar.displayLogo | default true) }} {{- if (.Site.Params.navbar.displayLogo | default true) }}
<img class="block dark:hidden" src="{{ $logoPath | relURL }}" alt="{{ .Site.Title }}" height="{{ $logoHeight }}" width="{{ $logoWidth }}" /> <img class="block dark:hidden" src="{{ $logoPath | relURL }}" alt="{{ .Site.Title }}" height="{{ $logoHeight }}" width="{{ $logoWidth }}" />
@ -22,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

@ -1,11 +1,11 @@
{{ with .Description | plainify -}} {{ with .Description | plainify | htmlUnescape -}}
{{ . -}} {{ . -}}
{{ else -}} {{ else -}}
{{ if .IsHome -}} {{ if .IsHome -}}
{{ with .Site.Params.description | plainify -}} {{ with .Site.Params.description | plainify | htmlUnescape -}}
{{ . -}} {{ . -}}
{{ end -}} {{ end -}}
{{ else -}} {{ else -}}
{{ .Summary | plainify | chomp -}} {{ .Summary | plainify | htmlUnescape | chomp -}}
{{ end -}} {{ end -}}
{{ end -}} {{ end -}}

View File

@ -0,0 +1,27 @@
{{/* Get page width from site configuration */}}
{{/* Default page width */}}
{{- $pageWidth := "" -}}
{{/* Get page width setting from page front matter or site params */}}
{{ with .Params.width -}}
{{ $pageWidth = . -}}
{{ else -}}
{{ with .Site.Params.page.width -}}
{{ $pageWidth = . -}}
{{ end -}}
{{ end -}}
{{- with $pageWidth -}}
{{ if eq . "wide" -}}
{{ $pageWidth = "max-w-[90rem]" -}}
{{ else if eq . "full" -}}
{{ $pageWidth = "max-w-full" -}}
{{ else -}}
{{ $pageWidth = "max-w-screen-xl" -}}
{{ end -}}
{{ else -}}
{{ $pageWidth = "max-w-screen-xl" -}}
{{ end -}}
{{ return $pageWidth }}

View File

@ -1,40 +0,0 @@
{{- $pctx := . -}}
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pctx.RegularPages -}}
{{- else -}}
{{- $pages = $pctx.Pages -}}
{{- end -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} of {{ end }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
<generator>Hugo -- gohugo.io</generator>
<language>{{ site.Language.LanguageCode }}</language>{{ with .Site.Author.email }}
<managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{- with .OutputFormats.Get "RSS" -}}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end -}}
{{ range $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
<guid>{{ .Permalink }}</guid>
<description>{{ with .Description | html }}{{ . }}{{ else }}{{ .Summary | html }}{{ end -}}</description>
<content:encoded>{{ (printf "<![CDATA[%s]]>" .Content) | safeHTML }}</content:encoded>
</item>
{{ end }}
</channel>
</rss>

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

@ -1,9 +1,42 @@
{{- $context := . -}}
{{- $link := .Get "link" -}} {{- $link := .Get "link" -}}
{{- $title := .Get "title" -}} {{- $title := .Get "title" -}}
{{- $icon := .Get "icon" -}} {{- $icon := .Get "icon" -}}
{{- $subtitle := .Get "subtitle" }} {{- $subtitle := .Get "subtitle" -}}
{{- $image := .Get "image" }} {{- $image := .Get "image" -}}
{{- $context := . -}} {{- $width := 0 -}}
{{- $height := 0 -}}
{{- $imageStyle := .Get "imageStyle" -}}
{{/* Image processing options */}}
{{- $method := .Get "method" | default "Resize" | humanize -}}
{{- $options := .Get "options" | default "800x webp q80" -}}
{{- if and $image (not (urls.Parse $image).Scheme) -}}
{{/* Process images in assets */}}
{{- with resources.Get $image -}}
{{- $processed := "" -}}
{{- if eq $method "Resize" -}}
{{- $processed = (.Resize $options) -}}
{{- else if eq $method "Fit" -}}
{{- $processed = (.Fit $options) -}}
{{- else if eq $method "Fill" -}}
{{- $processed = (.Fill $options) -}}
{{- else if eq $method "Crop" -}}
{{- $processed = (.Crop $options) -}}
{{- else -}}
{{- errorf "Invalid image processing command: Must be one of Crop, Fit, Fill or Resize." -}}
{{- end -}}
{{- $width = $processed.Width -}}
{{- $height = $processed.Height -}}
{{- $image = $processed.RelPermalink -}}
{{- else -}}
{{/* Otherwise, use relative link of the image */}}
{{- if hasPrefix $image "/" -}}
{{- $image = relURL (strings.TrimPrefix "/" $image) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{ $linkClass := "hover:border-gray-300 bg-transparent shadow-sm dark:border-neutral-800 hover:bg-slate-50 hover:shadow-md dark:hover:border-neutral-700 dark:hover:bg-neutral-900" }} {{ $linkClass := "hover:border-gray-300 bg-transparent shadow-sm dark:border-neutral-800 hover:bg-slate-50 hover:shadow-md dark:hover:border-neutral-700 dark:hover:bg-neutral-900" }}
{{- with $image -}} {{- with $image -}}
@ -13,6 +46,7 @@
{{- $external := strings.HasPrefix $link "http" -}} {{- $external := strings.HasPrefix $link "http" -}}
{{- $href := cond (strings.HasPrefix $link "/") ($link | relURL) $link -}} {{- $href := cond (strings.HasPrefix $link "/") ($link | relURL) $link -}}
<a <a
class="hextra-card group flex flex-col justify-start overflow-hidden rounded-lg border border-gray-200 text-current no-underline dark:shadow-none hover:shadow-gray-100 dark:hover:shadow-none shadow-gray-100 active:shadow-sm active:shadow-gray-200 transition-all duration-200 {{ $linkClass }}" class="hextra-card group flex flex-col justify-start overflow-hidden rounded-lg border border-gray-200 text-current no-underline dark:shadow-none hover:shadow-gray-100 dark:hover:shadow-none shadow-gray-100 active:shadow-sm active:shadow-gray-200 transition-all duration-200 {{ $linkClass }}"
href="{{ $href }}" href="{{ $href }}"
@ -21,7 +55,15 @@
{{- end -}} {{- end -}}
> >
{{- with $image -}} {{- with $image -}}
<img alt="{{ $title }}" loading="lazy" decoding="async" style="color: transparent;" src="{{ $image | safeURL }}" /> <img
alt="{{ $title }}"
loading="lazy"
decoding="async"
src="{{ $image | safeURL }}"
{{ with $width }}width="{{ . }}"{{ end }}
{{ with $height }}height="{{ . }}"{{ end }}
{{ with $imageStyle }}style="{{ . | safeCSS }}"{{ end }}
/>
{{- end -}} {{- end -}}
{{- $padding := "p-4" -}} {{- $padding := "p-4" -}}
@ -31,10 +73,10 @@
<span class="flex font-semibold items-start gap-2 {{ $padding }} text-gray-700 hover:text-gray-900 dark:text-neutral-200 dark:hover:text-neutral-50"> <span class="flex font-semibold items-start gap-2 {{ $padding }} text-gray-700 hover:text-gray-900 dark:text-neutral-200 dark:hover:text-neutral-50">
{{- with $icon }}{{ partial "utils/icon.html" (dict "name" $icon) -}}{{ end -}} {{- with $icon }}{{ partial "utils/icon.html" (dict "name" $icon) -}}{{- end -}}
{{- $title -}} {{- $title -}}
</span> </span>
{{- with $subtitle -}} {{- with $subtitle -}}
<div class="line-clamp-3 text-sm font-normal text-gray-500 dark:text-gray-400 px-4 mb-4 mt-2">{{ $subtitle }}</div> <div class="line-clamp-3 text-sm font-normal text-gray-500 dark:text-gray-400 px-4 mb-4 mt-2">{{- $subtitle | markdownify -}}</div>
{{- end -}} {{- end -}}
</a> </a>

View File

@ -0,0 +1,28 @@
{{- $title := .Get "title" -}}
{{- $subtitle := .Get "subtitle" -}}
{{- $class := .Get "class" -}}
{{- $image := .Get "image" -}}
{{- $imageClass := .Get "imageClass" -}}
{{- $style := .Get "style" -}}
{{- $icon := .Get "icon" -}}
<div
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
class="{{ $class }} hextra-feature-card relative overflow-hidden rounded-3xl border border-gray-200 hover:border-gray-300 dark:border-neutral-800 dark:hover:border-neutral-700 before:pointer-events-none before:absolute before:inset-0 before:bg-glass-gradient"
>
<div class="relative w-full p-6">
<h3 class="text-2xl font-medium leading-6 mb-2 flex items-center">
{{ with $icon -}}
<span class="pr-2">
{{- partial "utils/icon.html" (dict "name" . "attributes" "height=1.5rem") -}}
</span>
{{ end -}}
<span>{{ $title }}</span>
</h3>
<p class="text-gray-500 dark:text-gray-400 text-sm leading-6">{{ $subtitle | markdownify }}</p>
</div>
{{- with $image -}}
<img src="{{ . }}" class="absolute max-w-none {{ $imageClass }}" alt="{{ $title }}" />
{{- end -}}
</div>

View File

@ -0,0 +1,9 @@
{{- $style := .Get "style" -}}
<div
class="grid sm:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-4 w-full"
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
>
{{ .Inner }}
</div>

View File

@ -0,0 +1,15 @@
{{- $link := .Get "link" -}}
{{- $external := hasPrefix $link "http" -}}
{{- $href := cond (hasPrefix $link "/") ($link | relURL) $link -}}
{{- $class := .Get "class" }}
{{- $style := .Get "style" -}}
<a
href="{{ $href }}"
class="{{ $class }} inline-flex items-center rounded-full gap-2 px-3 py-1 text-xs text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-neutral-800 dark:border-neutral-800 border hover:border-gray-400 dark:hover:text-gray-50 dark:hover:border-gray-600 transition-all ease-in duration-200"
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
{{ if $external }}target="_blank" rel="noreferrer"{{ end -}}
>
{{ .Inner | markdownify }}
</a>

View File

@ -0,0 +1,15 @@
{{- $link := .Get "link" -}}
{{- $text := .Get "text" -}}
{{- $style := .Get "style" -}}
{{- $external := hasPrefix $link "http" -}}
{{- $href := cond (hasPrefix $link "/") ($link | relURL) $link -}}
<a
href="{{ $href }}"
class="font-medium cursor-pointer px-6 py-3 rounded-full text-center text-white inline-block bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800 transition-all ease-in duration-200"
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
{{ if $external }}target="_blank" rel="noreferrer"{{ end -}}
>
{{- $text -}}
</a>

View File

@ -0,0 +1,9 @@
{{- $style := .Get "style" -}}
<h1
class="text-4xl font-bold leading-none tracking-tighter md:text-5xl py-2 bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600 dark:from-gray-100 dark:to-gray-400"
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
>
{{ .Inner | markdownify }}
</h1>

View File

@ -0,0 +1,9 @@
{{- $style := .Get "style" -}}
<p
class="text-xl text-gray-600 dark:text-gray-400 sm:text-xl"
{{ with $style }}style="{{ . | safeCSS }}"{{ end }}
>
{{ .Inner | markdownify }}
</p>

View File

@ -1,9 +1,9 @@
{{- $name := .Get "name" | default (.Get 0) -}} {{- $name := .Get "name" | default (.Get 0) -}}
{{- $icon := index site.Data.icons (.Get 0) -}} {{- $icon := index site.Data.icons $name -}}
{{- $attributes := "height=1em"}} {{- $attributes := .Get "attributes" | default "height=1em"}}
{{- if not $icon -}} {{- if not $icon -}}
{{ errorf "icon %q not found" (.Get 0) }} {{ errorf "icon %q not found" $name }}
{{- end -}} {{- end -}}
{{- $icon = replaceRE "<svg" (printf "<svg %s" $attributes) $icon -}} {{- $icon = replaceRE "<svg" (printf "<svg %s" $attributes) $icon -}}

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} */
@ -14,6 +14,11 @@ module.exports = {
content: [ content: [
'./**/hugo_stats.json', './**/hugo_stats.json',
], ],
safelist: [
'max-w-screen-xl',
'max-w-[90rem]',
'max-w-full'
],
theme: { theme: {
screens: { screens: {
sm: '640px', sm: '640px',