Compare commits

...

72 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
00d26dee2c fix: swap navbar logo width and height (#58)
Height/Width swap issue.
2023-09-14 08:17:28 +01:00
Xin
e9ea9786e9 feat: enhance scrollbar style (#56)
* feat: enhance scrollbar style

* chore: set `color-scheme` for document element
2023-09-14 00:01:38 +01:00
84ac7fe773 docs: add example for customizing css for inline code element (#55)
* discussion #40

* discussion #46

* corrected Go code

* removed code block section

* changed heading to 'Inline Code Element'

---------

Co-authored-by: Saurabh Mishra <saurabh.m@mailfence.com>
2023-09-13 22:34:46 +01:00
Xin
a184cfd41e fix: use relURL only for link starts with slash (#54) 2023-09-13 05:40:00 -04:00
Xin
76ac694542 feat: add backdrop blur for nav bar (#53) 2023-09-13 10:02:38 +01:00
Xin
f70ba59ca0 fix: use relative url for card link (#52)
* fix: cards links should not redirect to 404

* docs: update card links

* fix: use relURL for card link
2023-09-13 09:49:01 +01:00
Xin
4a9a2850fc docs: add instruction for using markdown in tabs (#47) 2023-09-13 07:54:44 +01:00
Xin
4553a8eda2 docs: add more instruction for favicon (#44) 2023-09-11 17:15:24 -04:00
Xin
237d890f67 ci: use dynamic repo and username for baseURL 2023-09-10 15:20:17 +01:00
Xin
04e131f93a chore: normalize headings (#42)
* chore: minor update to card component

* chore: normalize headings of blog and single page
2023-09-10 14:54:30 +01:00
Xin
61e41f247b chore: add contributing guide
[skip ci]
2023-09-10 12:07:04 +01:00
6d00cb32b0 docs: add Logo, Favicon in Configurations (#37)
* Add Configuration for Logo, Favicon.

---------

Co-authored-by: Xin <xin@imfing.com>
2023-09-05 23:45:05 +01:00
939acc02a8 fix: add line break for steps shortcode example usage
* there needs to be a line break between {{% steps %}} and the first H3

If there is no line break between {{% steps %}}  and the first heading, the content is not rendered correctly.

* Update exampleSite/content/docs/guide/shortcodes/steps.md

* Update exampleSite/content/docs/guide/shortcodes/steps.md
2023-09-05 06:24:51 -04:00
Sid
e4c36236df ci: setup actions/checkout@v3 to fetch complete Git history (#32)
Attempt to fix #31
2023-09-03 19:05:33 +01:00
Xin
4381f31085 chore: make index page container wider (#30)
* chore: enable footer by default

* chore: make index container wider
2023-09-03 16:12:53 +01:00
Xin
171399889d docs: add instruction to update theme 2023-09-02 16:02:28 +01:00
Xin
3bcdf84ad4 feat: details shortcode (#28)
* feat: details shortcode

* docs: add details shortcode page

* chore: update shortcodes page

* docs: update

* Revert "chore: update shortcodes page"

This reverts commit c005ad4cb1.
2023-09-02 16:01:34 +01:00
91 changed files with 3153 additions and 2475 deletions

110
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,110 @@
# Contribute to Hextra
👋 Thank you for being interested in contributing to Hextra! As an open source project, we welcome contributions of many forms including bug reports, feature requests, documentation improvements, and code contributions.
<!-- omit in toc -->
## Table of Contents
- [Guidelines](#guidelines)
- [Contributing Code](#contributing-code)
- [Contributing Documentation](#contributing-documentation)
- [💬 GitHub Discussions](#-github-discussions)
- [GitHub Issues](#github-issues)
- [Development](#development)
- [Local development setup](#local-development-setup)
- [Project structure](#project-structure)
- [Start the development server](#start-the-development-server)
- [Compile the styles](#compile-the-styles)
## Guidelines
### Contributing Code
To contribute, please follow the ["Fork and Pull Request"][fork and pull] workflow
Fork the repository, make your changes, and then submit a pull request.
Please make sure to include a description of the changes you made and why you made them.
Use [Conventional Commits][conventional commits] message to make it easier to understand the changes you made.
### Contributing Documentation
Similar to contributing code, you can also contribute to the documentation by submitting a pull request.
The documentation site is located in the [`exampleSite`](../exampleSite/) folder.
You can make changes to the documentation and create a pull request. A preview of the new documentation will be automatically generated and displayed in the pull request comment via [Netlify][netlify deploy preview].
### 💬 GitHub Discussions
Were using [Discussions][discussions] as a place to connect with other members using Hextra:
- Ask questions youre wondering about.
- Share ideas.
- Engage with other users.
### GitHub Issues
If you find a bug or have a feature request, please [open an issue][issues].
Please make sure to include a description of the bug or feature you are requesting. If you are reporting a bug, please include steps to reproduce the bug.
We recommend that you search existing [issues][issues] or discussions before opening a new one to prevent duplicates.
## Development
> **Note**
> You can start developing on [GitHub Codespaces][open in codespaces] or use [devcontainer][devcontainer] locally without installing any dependencies.
### Local development setup
- [Hugo][hugo] >= v0.115.0 (extended version)
- [Node.js][nodejs]
- [Go][go]
Install dependencies:
```bash
npm i
```
### Project structure
- [`assets`](../assets/): CSS styles and JavaScript files.
- [`data`](../data/): The theme data files. Now only contains the `icons.yaml` file.
- [`exampleSite`](../exampleSite/): The documentation site for the theme.
- [`i18n`](../i18n/): The theme translation files.
- [`layouts`](../layouts/): The theme layouts.
- [`static`](../static/): The static files for the theme. For example, the favicon and the site logo.
Please refer to the [Hugo documentation][hugo] for more information.
### Start the development server
```bash
npm run dev:theme
```
It will start the Hugo server on `http://localhost:1313/` for the `exampleSite` content.
### Compile the styles
For development preview, we compile the Tailwind CSS styles on the fly. But for production, we need to compile the styles first.
```bash
npm run build:css
```
It will compile the Tailwind CSS styles and generate the `assets/css/compiled/main.css` file.
<!--links-->
[fork and pull]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects
[conventional commits]: https://www.conventionalcommits.org
[issues]: https://github.com/imfing/hextra/issues
[discussions]: https://github.com/imfing/hextra/discussions
[nodejs]: https://nodejs.org/en/
[hugo]: https://gohugo.io/
[go]: https://golang.org/doc/install
[devcontainer]: https://code.visualstudio.com/docs/devcontainers/containers
[open in codespaces]: https://codespaces.new/imfing/hextra
[netlify deploy preview]: https://docs.netlify.com/site-deploys/deploy-previews/

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,9 @@ 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:
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
@ -53,7 +55,7 @@ jobs:
hugo \ hugo \
--minify \ --minify \
--themesDir=../.. --source=exampleSite \ --themesDir=../.. --source=exampleSite \
--baseURL "https://imfing.github.io/hextra/" --baseURL "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/"
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v2 uses: actions/upload-pages-artifact@v2
with: with:

View File

@ -1,4 +1,5 @@
{ {
"editor.tabSize": 2, "editor.tabSize": 2,
"css.customData": [".vscode/tailwind.json"] "css.customData": [".vscode/tailwind.json"],
"markdown.extension.toc.levels": "2..6"
} }

View File

@ -31,13 +31,16 @@ Using the [Hextra Starter Template](https://github.com/imfing/hextra-starter-tem
The template repository also includes a [GitHub Actions workflow](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow) for deploying your website to GitHub Pages. The template repository also includes a [GitHub Actions workflow](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow) for deploying your website to GitHub Pages.
<img alt="Hextra Starter Template" src="https://user-images.githubusercontent.com/5097752/263551418-c403b9a9-a76c-47a6-8466-513d772ef0b7.jpg" width=600/>
### Usage ### Usage
Refer to the [documentation](https://imfing.github.io/hextra/docs) for more information. Refer to the [documentation](https://imfing.github.io/hextra/docs) for more information.
## Contributing ## Contributing
This project is actively under development. Contributions are welcome! Contributions are welcome!
Check out the [contributing guide](.github/CONTRIBUTING.md) to get started.
## License ## License

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,14 @@ nav {
} }
} }
@supports (
(-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))
) {
.nav-container-blur {
@apply backdrop-blur-md bg-white/[.85] dark:!bg-dark/80;
}
}
.hamburger-menu svg { .hamburger-menu svg {
g { g {
@apply origin-center; @apply origin-center;

View File

@ -0,0 +1,21 @@
.hextra-scrollbar {
scrollbar-width: thin; /* Firefox */
scrollbar-color: oklch(55.55% 0 0 / 40%) transparent; /* Firefox */
scrollbar-gutter: stable;
&::-webkit-scrollbar {
@apply w-3 h-3;
}
&::-webkit-scrollbar-track {
@apply bg-transparent;
}
&::-webkit-scrollbar-thumb {
@apply rounded-[10px];
}
&:hover::-webkit-scrollbar-thumb {
border: 3px solid transparent;
background-color: var(--tw-shadow-color);
background-clip: content-box;
@apply shadow-neutral-500/20 hover:shadow-neutral-500/40;
}
}

View File

@ -1,3 +1,5 @@
@import "tailwind.css";
@import "typography.css"; @import "typography.css";
@import "highlight.css"; @import "highlight.css";
@import "components/cards.css"; @import "components/cards.css";
@ -5,10 +7,7 @@
@import "components/search.css"; @import "components/search.css";
@import "components/sidebar.css"; @import "components/sidebar.css";
@import "components/navbar.css"; @import "components/navbar.css";
@import "components/scrollbar.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
html { html {
@apply text-base antialiased; @apply text-base antialiased;
@ -22,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%;
} }

3
assets/css/tailwind.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

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,36 +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") {
localStorage.setItem("color-theme", "dark"); setDarkTheme();
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")) {
localStorage.setItem("color-theme", "light"); setLightTheme();
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");
localStorage.setItem("color-theme", "light");
} else {
document.documentElement.classList.add("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

@ -10,7 +10,11 @@ This page describes the available options and how to customize the theme further
## Custom CSS ## Custom CSS
To add custom CSS, we need to create a file `assets/css/custom.css` in our site. Hextra will automatically load this file. For example, customize the font family of the content: To add custom CSS, we need to create a file `assets/css/custom.css` in our site. Hextra will automatically load this file.
### Font Family
The font family of the content can be customized using:
```css {filename="assets/css/custom.css"} ```css {filename="assets/css/custom.css"}
.content { .content {
@ -18,13 +22,24 @@ To add custom CSS, we need to create a file `assets/css/custom.css` in our site.
} }
``` ```
### Inline Code Element
The color of text mixed with `other text` can customized with:
```css {filename="assets/css/custom.css"}
.content code:not(.code-block code) {
color: #c97c2e;
}
```
### 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,6 +84,102 @@ Voila! You can see your new site at `http://localhost:1313/`.
{{% /steps %}} {{% /steps %}}
{{% details title="How to update theme?" %}}
To update all Hugo modules in your project to their latest versions, run the following command:
```shell
$ 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.
{{% /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
Explore the following sections to start adding more contents: Explore the following sections to start adding more contents:

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-->
@ -65,6 +65,24 @@ There are different types of menu items:
These menu items can be sorted by setting the `weight` parameter. These menu items can be sorted by setting the `weight` parameter.
### Logo and Title
To modify the default logo, edit `hugo.yaml` and add the path to your logo file under `static` directory.
Optionally, you can change the link that users are redirected to when clicking on your logo, as well as set the width & height of the logo in pixels.
```yaml {filename="hugo.yaml"}
params:
navbar:
displayTitle: true
displayLogo: true
logo:
path: images/logo.svg
dark: images/logo-dark.svg
link: /
width: 40
height: 20
```
## Sidebar ## Sidebar
### Main Sidebar ### Main Sidebar
@ -137,3 +155,105 @@ copyright: "© 2023 YOUR TEXT HERE"
``` ```
For your reference, an example [`i18n/en.yaml`](https://github.com/imfing/hextra/blob/main/i18n/en.yaml) file can be found in the GitHub repository. Additionally, you could use Markdown format in the copyright text. For your reference, an example [`i18n/en.yaml`](https://github.com/imfing/hextra/blob/main/i18n/en.yaml) file can be found in the GitHub repository. Additionally, you could use Markdown format in the copyright text.
## Others
### Favicon
To customize the [favicon](https://en.wikipedia.org/wiki/Favicon) for your site, place icon files under the `static` folder to override the [default favicons from the theme](https://github.com/imfing/hextra/tree/main/static):
{{< filetree/container >}}
{{< filetree/folder name="static" >}}
{{< filetree/file name="android-chrome-192x192.png" >}}
{{< filetree/file name="android-chrome-512x512.png" >}}
{{< filetree/file name="apple-touch-icon.png" >}}
{{< filetree/file name="favicon-16x16.png" >}}
{{< filetree/file name="favicon-32x32.png" >}}
{{< filetree/file name="favicon-dark.svg" >}}
{{< filetree/file name="favicon.ico" >}}
{{< filetree/file name="favicon.svg" >}}
{{< filetree/file name="site.webmanifest" >}}
{{< /filetree/folder >}}
{{< /filetree/container >}}
Include both `favicon.ico` and `favicon.svg` files in your project to ensure your site's favicons display correctly.
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.
### 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

@ -13,6 +13,7 @@ Hextra provides a collection of beautiful shortcodes to enhance your content.
{{< cards >}} {{< cards >}}
{{< card link="callout" title="Callout" icon="warning" >}} {{< card link="callout" title="Callout" icon="warning" >}}
{{< card link="cards" title="Cards" icon="card" >}} {{< card link="cards" title="Cards" icon="card" >}}
{{< card link="details" title="Details" icon="chevron-right" >}}
{{< card link="filetree" title="FileTree" icon="folder-tree" >}} {{< card link="filetree" title="FileTree" icon="folder-tree" >}}
{{< card link="icon" title="Icon" icon="badge-check" >}} {{< card link="icon" title="Icon" icon="badge-check" >}}
{{< card link="steps" title="Steps" icon="one" >}} {{< card link="steps" title="Steps" icon="one" >}}

View File

@ -6,20 +6,21 @@ linkTitle: Cards
## Example ## Example
{{< cards >}} {{< cards >}}
{{< card link="/" title="Callout" icon="warning" >}} {{< card link="../callout" title="Callout" icon="warning" >}}
{{< card link="/" title="No Icon" >}} {{< card link="/" title="No Icon" >}}
{{< /cards >}} {{< /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
``` ```
{{</* cards */>}} {{</* cards */>}}
{{</* card link="/" title="Callout" icon="warning" */>}} {{</* card link="../callout" title="Callout" icon="warning" */>}}
{{</* card link="/" title="No Icon" */>}} {{</* card link="/" title="No Icon" */>}}
{{</* /cards */>}} {{</* /cards */>}}
``` ```
@ -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,43 @@
---
title: Details
---
A built-in component to display a collapsible content.
<!--more-->
## Example
{{% details title="Details" %}}
This is the content of the details.
Markdown is **supported**.
{{% /details %}}
{{% details title="Click me to reveal" closed="true" %}}
This will be hidden by default.
{{% /details %}}
## Usage
````
{{%/* details title="Details" */%}}
This is the content of the details.
Markdown is **supported**.
{{%/* /details */%}}
````
````
{{%/* details title="Click me to reveal" closed="true" */%}}
This will be hidden by default.
{{%/* /details */%}}
````

View File

@ -29,6 +29,7 @@ Put Markdown h3 header within `steps` shortcode.
``` ```
{{%/* steps */%}} {{%/* steps */%}}
### Step 1 ### Step 1
This is the first step. This is the first step.
@ -36,5 +37,6 @@ This is the first step.
### Step 2 ### Step 2
This is the second step. This is the second step.
{{%/* /steps */%}} {{%/* /steps */%}}
``` ```

View File

@ -50,3 +50,44 @@ The `YAML` tab will be selected by default.
{{< tab >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}} {{< tab >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}}
{{< /tabs >}} {{< /tabs >}}
### Use Markdown
Markdown syntax including code block is also supported:
````
{{</* tabs items="JSON,YAML,TOML" */>}}
{{</* tab */>}}
```json
{ "hello": "world" }
```
{{</* /tab */>}}
... add other tabs similarly
{{</* /tabs */>}}
````
{{< tabs items="JSON,YAML,TOML" >}}
{{< tab >}}
```json
{ "hello": "world" }
```
{{< /tab >}}
{{< tab >}}
```yaml
hello: world
```
{{< /tab >}}
{{< tab >}}
```toml
hello = "world"
```
{{< /tab >}}
{{< /tabs >}}

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

@ -11,6 +11,7 @@
"cite", "cite",
"code", "code",
"del", "del",
"details",
"div", "div",
"em", "em",
"figcaption", "figcaption",
@ -39,11 +40,11 @@
"path", "path",
"pre", "pre",
"script", "script",
"section",
"span", "span",
"strong", "strong",
"style", "style",
"sub", "sub",
"summary",
"sup", "sup",
"svg", "svg",
"table", "table",
@ -80,17 +81,21 @@
"before:bg-gray-200", "before:bg-gray-200",
"before:content-[\"\"]", "before:content-[\"\"]",
"before:content-['#']", "before:content-['#']",
"before:content-['']",
"before:inline-block",
"before:inset-0", "before:inset-0",
"before:inset-y-1", "before:inset-y-1",
"before:mr-1",
"before:opacity-25", "before:opacity-25",
"before:pointer-events-none", "before:pointer-events-none",
"before:transition-transform",
"before:w-px", "before:w-px",
"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-orange-50", "bg-orange-50",
"bg-primary-100", "bg-primary-100",
"bg-primary-400", "bg-primary-400",
@ -107,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",
@ -122,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",
@ -147,9 +154,11 @@
"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",
"dark:before:invert",
"dark:bg-blue-900/30", "dark:bg-blue-900/30",
"dark:bg-dark", "dark:bg-dark",
"dark:bg-dark/50", "dark:bg-dark/50",
@ -165,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",
@ -178,9 +188,11 @@
"dark:hidden", "dark:hidden",
"dark:hover:bg-gray-100/5", "dark:hover:bg-gray-100/5",
"dark:hover:bg-neutral-700", "dark:hover:bg-neutral-700",
"dark:hover:bg-neutral-800",
"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",
@ -219,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",
@ -237,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",
@ -250,12 +263,14 @@
"group-data-[theme=dark]:hidden", "group-data-[theme=dark]:hidden",
"group-data-[theme=light]:hidden", "group-data-[theme=light]:hidden",
"group-hover/code:opacity-100", "group-hover/code:opacity-100",
"group-open:before:rotate-90",
"group/code", "group/code",
"group/copybtn", "group/copybtn",
"grow", "grow",
"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",
@ -264,9 +279,11 @@
"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",
"hextra-scrollbar",
"hextra-sidebar-collapsible-button", "hextra-sidebar-collapsible-button",
"hextra-toc", "hextra-toc",
"hidden", "hidden",
@ -279,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",
@ -305,9 +323,11 @@
"justify-start", "justify-start",
"language-options", "language-options",
"language-switcher", "language-switcher",
"last-of-type:mb-0",
"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]",
@ -355,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))]",
@ -396,6 +417,8 @@
"mx-auto", "mx-auto",
"my-1.5", "my-1.5",
"my-2", "my-2",
"nav-container",
"nav-container-blur",
"next-error-h1", "next-error-h1",
"no-underline", "no-underline",
"not-prose", "not-prose",
@ -412,11 +435,11 @@
"overflow-y-auto", "overflow-y-auto",
"overscroll-contain", "overscroll-contain",
"p-0.5", "p-0.5",
"p-1",
"p-1.5", "p-1.5",
"p-2", "p-2",
"p-4", "p-4",
"p-6", "p-6",
"pb-6",
"pb-8", "pb-8",
"pb-[env(safe-area-inset-bottom)]", "pb-[env(safe-area-inset-bottom)]",
"pb-px", "pb-px",
@ -425,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",
@ -461,6 +486,7 @@
"rtl:-ml-4", "rtl:-ml-4",
"rtl:-rotate-180", "rtl:-rotate-180",
"rtl:before:right-0", "rtl:before:right-0",
"rtl:before:rotate-180",
"rtl:left-1.5", "rtl:left-1.5",
"rtl:left-3", "rtl:left-3",
"rtl:md:right-auto", "rtl:md:right-auto",
@ -497,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",
@ -523,6 +548,7 @@
"text-gray-800", "text-gray-800",
"text-gray-900", "text-gray-900",
"text-left", "text-left",
"text-lg",
"text-orange-800", "text-orange-800",
"text-primary-800", "text-primary-800",
"text-red-900", "text-red-900",
@ -565,4 +591,4 @@
], ],
"ids": null "ids": null
} }
} }

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 not .Site.Params.footer.disabled }}{{ 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,14 +1,17 @@
{{ 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)]">
<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" />
<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="mb-16"></div> <div class="mb-16"></div>
<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,16 +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">
<h1 class="text-4xl tracking-tighter text-center font-extrabold md:text-5xl mt-8 pb-6">{{ .Title }}</h1> <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> <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-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)]">

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,16 +16,41 @@
{{ 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");
} else { document.documentElement.style.colorScheme = "dark";
}
const setLightTheme = () => {
document.documentElement.classList.remove("dark"); document.documentElement.classList.remove("dark");
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>

View File

@ -4,15 +4,23 @@
{{- $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="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="pointer-events-none absolute z-[-1] h-full w-full bg-white 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:bg-dark 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="{{ $logoWidth }}" width="{{ $logoHeight }}" /> <img class="block dark:hidden" src="{{ $logoPath | relURL }}" alt="{{ .Site.Title }}" height="{{ $logoHeight }}" width="{{ $logoWidth }}" />
<img class="hidden dark:block" src="{{ $logoDarkPath | relURL }}" alt="{{ .Site.Title }}" height="{{ $logoWidth }}" width="{{ $logoHeight }}" /> <img class="hidden dark:block" src="{{ $logoDarkPath | relURL }}" alt="{{ .Site.Title }}" height="{{ $logoHeight }}" width="{{ $logoWidth }}" />
{{- end }} {{- end }}
{{- if (.Site.Params.navbar.displayTitle | default true) }} {{- if (.Site.Params.navbar.displayTitle | default true) }}
<span class="mx-2 font-extrabold inline select-none" title="{{ .Site.Title }}">{{- .Site.Title -}}</span> <span class="mx-2 font-extrabold inline select-none" title="{{ .Site.Title }}">{{- .Site.Title -}}</span>
@ -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,13 +13,13 @@
<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>
<div> <div>
<ul <ul
class="search-results hidden border border-gray-200 bg-white text-gray-100 dark:border-neutral-800 dark:bg-neutral-900 absolute top-full z-20 mt-2 overflow-auto overscroll-contain rounded-xl py-2.5 shadow-xl max-h-[min(calc(50vh-11rem-env(safe-area-inset-bottom)),400px)] md:max-h-[min(calc(100vh-5rem-env(safe-area-inset-bottom)),400px)] inset-x-0 ltr:md:left-auto rtl:md:right-auto contrast-more:border contrast-more:border-gray-900 contrast-more:dark:border-gray-50 w-screen min-h-[100px] max-w-[min(calc(100vw-2rem),calc(100%+20rem))]" class="search-results hextra-scrollbar hidden border border-gray-200 bg-white text-gray-100 dark:border-neutral-800 dark:bg-neutral-900 absolute top-full z-20 mt-2 overflow-auto overscroll-contain rounded-xl py-2.5 shadow-xl max-h-[min(calc(50vh-11rem-env(safe-area-inset-bottom)),400px)] md:max-h-[min(calc(100vh-5rem-env(safe-area-inset-bottom)),400px)] inset-x-0 ltr:md:left-auto rtl:md:right-auto contrast-more:border contrast-more:border-gray-900 contrast-more:dark:border-gray-50 w-screen min-h-[100px] max-w-[min(calc(100vw-2rem),calc(100%+20rem))]"
style="transition: max-height 0.2s ease 0s;" style="transition: max-height 0.2s ease 0s;"
></ul> ></ul>
</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="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,10 +3,11 @@
{{- $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 }}
<div class="sticky top-16 overflow-y-auto pr-4 pt-6 text-sm [hyphens:auto] max-h-[calc(100vh-var(--navbar-height)-env(safe-area-inset-bottom))] ltr:-mr-4 rtl:-ml-4"> <div class="hextra-scrollbar sticky top-16 overflow-y-auto pr-4 pt-6 text-sm [hyphens:auto] max-h-[calc(100vh-var(--navbar-height)-env(safe-area-inset-bottom))] ltr:-mr-4 rtl:-ml-4">
{{- with .Fragments.Headings -}} {{- with .Fragments.Headings -}}
<p class="mb-4 font-semibold tracking-tight">{{ $onThisPage }}</p> <p class="mb-4 font-semibold tracking-tight">{{ $onThisPage }}</p>
{{- range . -}} {{- range . -}}
@ -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 -}}
@ -11,29 +44,39 @@
{{- end -}} {{- end -}}
{{- $external := strings.HasPrefix $link "http" -}} {{- $external := strings.HasPrefix $link "http" -}}
{{- $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="{{ $link }}" href="{{ $href }}"
{{- if $external -}} {{- if $external }}
target="_blank" target="_blank" rel="noreferrer"
rel="noreferrer"
{{- end -}} {{- end -}}
> >
{{- with $image -}} {{- with $image -}}
<img alt="{{ $title }}" loading="lazy" decoding="async" style="color: transparent;" src="{{ $image }}" /> <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" -}}
{{- with $subtitle -}} {{- with $subtitle -}}
{{ $padding = "pt-4 px-4"}} {{- $padding = "pt-4 px-4" -}}
{{- end -}} {{- end -}}
<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

@ -1,8 +1,8 @@
{{- $title := .Get "title" | default "" -}} {{- $title := .Get "title" | default "" -}}
{{- $closed := eq (.Get "closed") "true" | default false -}} {{- $closed := eq (.Get "closed") "true" | default false -}}
<details class="last-of-type:mb-0 rounded-lg bg-neutral-50 dark:bg-neutral-800 p-2 mt-4" data-expanded="true" {{ if not $closed }}open{{ end }}> <details class="last-of-type:mb-0 rounded-lg bg-neutral-50 dark:bg-neutral-800 p-2 mt-4 group" {{ if not $closed }}open{{ end }}>
<summary class="flex items-center cursor-pointer select-none list-none p-1 transition-colors hover:bg-gray-100 dark:hover:bg-neutral-800 before:mr-1 before:inline-block before:transition-transform before:content-[''] dark:before:invert rtl:before:rotate-180 [[data-expanded]>&]:before:rotate-90"> <summary class="flex items-center cursor-pointer select-none list-none p-1 rounded transition-colors hover:bg-gray-100 dark:hover:bg-neutral-800 before:mr-1 before:inline-block before:transition-transform before:content-[''] dark:before:invert rtl:before:rotate-180 group-open:before:rotate-90">
<strong class="text-lg">{{ $title | markdownify }}</strong> <strong class="text-lg">{{ $title | markdownify }}</strong>
</summary> </summary>
<div class="p-2 overflow-hidden"> <div class="p-2 overflow-hidden">

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',