mirror of
https://github.com/imfing/hextra.git
synced 2025-10-24 11:30:15 -04:00
Compare commits
11 Commits
v0.11.1
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e56169291c | ||
|
|
3551a56b8c | ||
|
|
bfeae19076 | ||
|
|
b7f4bffce6 | ||
|
|
708358de80 | ||
|
|
8699deb1dd | ||
|
|
a03dbf463f | ||
|
|
1c06ae5580 | ||
|
|
ccb63d60f1 | ||
|
|
3bc454bbf6 | ||
|
|
1b536e27a5 |
File diff suppressed because one or more lines are too long
@@ -33,19 +33,19 @@
|
||||
@apply hx:border-black/4 hx:bg-black/3 hx:break-words hx:rounded-md hx:border hx:py-0.5 hx:px-[.25em] hx:text-[.9em] hx:dark:border-white/10 hx:dark:bg-white/10;
|
||||
}
|
||||
:where(table):not(:where(.hextra-code-block table, [class~=not-prose],[class~=not-prose] *)) {
|
||||
@apply hx:block hx:overflow-x-auto hx:my-6 hx:p-0 hx:first:mt-0 hx:w-full hx:text-sm hx:leading-5;
|
||||
@apply hx:block hx:overflow-x-auto hx:my-6 hx:p-0 hx:first:mt-0 hx:w-full hx:text-sm hx:leading-5 hx:border-collapse;
|
||||
|
||||
thead {
|
||||
@apply hx:border-b hx:border-gray-200 hx:dark:border-neutral-800;
|
||||
@apply hx:bg-gray-50 hx:dark:bg-gray-600/20;
|
||||
}
|
||||
tbody tr {
|
||||
@apply hx:m-0 hx:border-b hx:border-gray-100 hx:dark:border-neutral-800/50;
|
||||
tr {
|
||||
@apply hx:m-0 hx:border-t hx:border-gray-300 hx:p-0 hx:dark:border-gray-600;
|
||||
}
|
||||
th {
|
||||
@apply hx:m-0 hx:p-2 hx:font-semibold hx:first:pl-0 hx:last:pr-0;
|
||||
@apply hx:m-0 hx:border hx:border-gray-300 hx:p-2 hx:font-semibold hx:dark:border-gray-600;
|
||||
}
|
||||
td {
|
||||
@apply hx:m-0 hx:p-2 hx:first:pl-0 hx:last:pr-0;
|
||||
@apply hx:m-0 hx:border hx:border-gray-300 hx:p-2 hx:dark:border-gray-600;
|
||||
}
|
||||
}
|
||||
:where(ol):not(:where([class~=not-prose],[class~=not-prose] *)) {
|
||||
|
||||
6
assets/js/head/banner.js
Normal file
6
assets/js/head/banner.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// The section must not be in the banner.js (body) file because it can create a quick flash.
|
||||
|
||||
if (localStorage.getItem('{{ site.Params.banner.key | default `banner-closed` }}')) {
|
||||
document.documentElement.style.setProperty("--hextra-banner-height", "0px");
|
||||
document.documentElement.classList.add("hextra-banner-hidden");
|
||||
}
|
||||
14
assets/js/head/theme.js
Normal file
14
assets/js/head/theme.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// The section must not be in the theme.js (body) file because it can create a quick flash (switch between light and dark).
|
||||
|
||||
function setTheme(theme) {
|
||||
document.documentElement.classList.remove("light", "dark");
|
||||
|
||||
if (theme !== "light" && theme !== "dark") {
|
||||
theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
document.documentElement.classList.add(theme);
|
||||
document.documentElement.style.colorScheme = theme;
|
||||
}
|
||||
|
||||
setTheme("color-theme" in localStorage ? localStorage.getItem("color-theme") : '{{ site.Params.theme.default | default `system`}}')
|
||||
2
build.sh
2
build.sh
@@ -9,7 +9,7 @@ echo "Using base URL: $BASE_URL"
|
||||
# Version configuration - modify these arrays to specify versions to build
|
||||
# MAIN_VERSION format: "ref:display_name:source_dir"
|
||||
# VERSIONS format: "ref:display_name:source_dir" where source_dir is either "docs" or "exampleSite"
|
||||
MAIN_VERSION="v0.11.0:latest:exampleSite"
|
||||
MAIN_VERSION="v0.11.1:latest:docs"
|
||||
VERSIONS=(
|
||||
"main:latest:docs" # latest version always builds from main
|
||||
"v0.10.2:v0.10:exampleSite"
|
||||
|
||||
@@ -164,6 +164,21 @@ menu:
|
||||
weight: 3
|
||||
```
|
||||
|
||||
### Hiding
|
||||
|
||||
Hiding the sidebar can be done using front matter:
|
||||
|
||||
```yaml {filename="content/docs/guide/configuration.md"}
|
||||
---
|
||||
title: Configuration
|
||||
sidebar:
|
||||
hide: true
|
||||
---
|
||||
```
|
||||
|
||||
This will hide the main sidebar from the page, freeing up space for the main content of the page.
|
||||
|
||||
|
||||
## Right Sidebar
|
||||
|
||||
### Table of Contents
|
||||
@@ -368,16 +383,6 @@ excludeSearch: true
|
||||
---
|
||||
```
|
||||
|
||||
### Google Analytics
|
||||
|
||||
To enable [Google Analytics](https://marketingplatform.google.com/about/analytics/), set `services.googleAnalytics.ID` flag in `hugo.yaml`:
|
||||
|
||||
```yaml {filename="hugo.yaml"}
|
||||
services:
|
||||
googleAnalytics:
|
||||
ID: G-MEASUREMENT_ID
|
||||
```
|
||||
|
||||
### Google Search Index
|
||||
|
||||
To [block Google Search](https://developers.google.com/search/docs/crawling-indexing/block-indexing) from indexing a page, set `noindex` to true in your page frontmatter:
|
||||
@@ -394,7 +399,25 @@ To exclude an entire directory, use the [`cascade`](https://gohugo.io/configurat
|
||||
> To block search crawlers, you can make a [`robots.txt` template](https://gohugo.io/templates/robots/).
|
||||
> However, `robots.txt` instructions do not necessarily keep a page out of Google search results.
|
||||
|
||||
### Umami Analytics
|
||||
### Analytics
|
||||
|
||||
Hextra has support for several different analytics solutions. Hextra only supports analytics in production environments. This is to ensure that you do not accidentally send analytic events when working locally. If, however, you do want to test analytics locally, you can run a production server using:
|
||||
|
||||
```
|
||||
hugo server --environment production
|
||||
```
|
||||
|
||||
#### Google Analytics
|
||||
|
||||
To enable [Google Analytics](https://marketingplatform.google.com/about/analytics/), set `services.googleAnalytics.ID` flag in `hugo.yaml`:
|
||||
|
||||
```yaml {filename="hugo.yaml"}
|
||||
services:
|
||||
googleAnalytics:
|
||||
ID: G-MEASUREMENT_ID
|
||||
```
|
||||
|
||||
#### Umami Analytics
|
||||
|
||||
To enable [Umami](https://umami.is/docs/), set `params.analytics.umami.serverURL` and `params.analytics.umami.websiteID` flag in `hugo.yaml`:
|
||||
|
||||
@@ -404,7 +427,7 @@ params:
|
||||
umami:
|
||||
serverURL: "https://example.com"
|
||||
websiteID: "94db1cb1-74f4-4a40-ad6c-962362670409"
|
||||
# scriptName: "umami.js" # optional (default: umami.js)
|
||||
# scriptName: "script.js" # optional (default: script.js)
|
||||
# https://umami.is/docs/tracker-configuration#data-host-url
|
||||
# hostURL: "http://stats.example.org" # optional
|
||||
# https://umami.is/docs/tracker-configuration#data-auto-track
|
||||
@@ -421,7 +444,7 @@ params:
|
||||
# doNotTrack: "true" # optional
|
||||
```
|
||||
|
||||
### Matomo Analytics
|
||||
#### Matomo Analytics
|
||||
|
||||
To enable [Matomo](https://matomo.org/), set `params.analytics.matomo.URL` and `params.analytics.matomo.ID` flag in `hugo.yaml`:
|
||||
|
||||
@@ -433,6 +456,32 @@ params:
|
||||
websiteID: "94db1cb1-74f4-4a40-ad6c-962362670409"
|
||||
```
|
||||
|
||||
#### GoatCounter Analytics
|
||||
|
||||
To enable [GoatCounter](https://www.goatcounter.com/), set `params.analytics.goatCounter.code` in `hugo.yaml`
|
||||
All settings available here are mirrors of the settings described in GoatCounter [settings](https://www.goatcounter.com/help/js#settings-44186)
|
||||
|
||||
```yaml {filename="hugo.yaml"}
|
||||
params:
|
||||
analytics:
|
||||
goatCounter:
|
||||
code: "ABCDE"
|
||||
|
||||
# Optional Settings
|
||||
#------------------
|
||||
# disables automatic collection of data
|
||||
# noOnload: true
|
||||
|
||||
# disables event binding. See more here https://www.goatcounter.com/help/events
|
||||
# noEvents: true
|
||||
|
||||
# allows data collection from local addresses. Use this with a production environment to test locally
|
||||
# allowLocal: true
|
||||
|
||||
# Allow data collection when a page is loaded in a frame or iframe
|
||||
# allowFrame: true
|
||||
```
|
||||
|
||||
### LLMS.txt Support
|
||||
|
||||
To enable [llms.txt](https://llmstxt.org/) output format for your site, which provides a structured text outline for [large language models](https://en.wikipedia.org/wiki/Large_language_model) and AI agents, add the `llms` output format to your site's `hugo.yaml`:
|
||||
|
||||
@@ -8,36 +8,36 @@ title: جزئیات
|
||||
|
||||
## مثال
|
||||
|
||||
{{% details title="جزئیات" %}}
|
||||
{{< details title="جزئیات" >}}
|
||||
|
||||
این محتوای جزئیات است.
|
||||
|
||||
مارکداون **پشتیبانی میشود**.
|
||||
|
||||
{{% /details %}}
|
||||
{{< /details >}}
|
||||
|
||||
{{% details title="برای نمایش کلیک کنید" closed="true" %}}
|
||||
{{< details title="برای نمایش کلیک کنید" closed="true" >}}
|
||||
|
||||
این بهصورت پیشفرض مخفی خواهد بود.
|
||||
|
||||
{{% /details %}}
|
||||
{{< /details >}}
|
||||
|
||||
## نحوه استفاده
|
||||
|
||||
````markdown
|
||||
{{%/* details title="جزئیات" */%}}
|
||||
{{</* details title="جزئیات" */>}}
|
||||
|
||||
این محتوای جزئیات است.
|
||||
|
||||
مارکداون **پشتیبانی میشود**.
|
||||
|
||||
{{%/* /details */%}}
|
||||
{{</* /details */>}}
|
||||
````
|
||||
|
||||
````markdown
|
||||
{{%/* details title="برای نمایش کلیک کنید" closed="true" */%}}
|
||||
{{</* details title="برای نمایش کلیک کنید" closed="true" */>}}
|
||||
|
||||
این بهصورت پیشفرض مخفی خواهد بود.
|
||||
|
||||
{{%/* /details */%}}
|
||||
{{</* /details */>}}
|
||||
````
|
||||
@@ -8,36 +8,36 @@ title: 詳細
|
||||
|
||||
## 例
|
||||
|
||||
{{% details title="詳細" %}}
|
||||
{{< details title="詳細" >}}
|
||||
|
||||
これは詳細のコンテンツです。
|
||||
|
||||
Markdown は **サポートされています**。
|
||||
|
||||
{{% /details %}}
|
||||
{{< /details >}}
|
||||
|
||||
{{% details title="クリックして表示" closed="true" %}}
|
||||
{{< details title="クリックして表示" closed="true" >}}
|
||||
|
||||
これはデフォルトで非表示になります。
|
||||
|
||||
{{% /details %}}
|
||||
{{< /details >}}
|
||||
|
||||
## 使用方法
|
||||
|
||||
````markdown
|
||||
{{%/* details title="詳細" */%}}
|
||||
{{</* details title="詳細" */>}}
|
||||
|
||||
これは詳細のコンテンツです。
|
||||
|
||||
Markdown は **サポートされています**。
|
||||
|
||||
{{%/* /details */%}}
|
||||
{{</* /details */>}}
|
||||
````
|
||||
|
||||
````markdown
|
||||
{{%/* details title="クリックして表示" closed="true" */%}}
|
||||
{{</* details title="クリックして表示" closed="true" */>}}
|
||||
|
||||
これはデフォルトで非表示になります。
|
||||
|
||||
{{%/* /details */%}}
|
||||
{{</* /details */>}}
|
||||
````
|
||||
@@ -8,36 +8,36 @@ A built-in component to display a collapsible content.
|
||||
|
||||
## Example
|
||||
|
||||
{{% details title="Details" %}}
|
||||
{{< details title="Details" >}}
|
||||
|
||||
This is the content of the details.
|
||||
|
||||
Markdown is **supported**.
|
||||
|
||||
{{% /details %}}
|
||||
{{< /details >}}
|
||||
|
||||
{{% details title="Click me to reveal" closed="true" %}}
|
||||
{{< details title="Click me to reveal" closed="true" >}}
|
||||
|
||||
This will be hidden by default.
|
||||
|
||||
{{% /details %}}
|
||||
{{< /details >}}
|
||||
|
||||
## Usage
|
||||
|
||||
````markdown
|
||||
{{%/* details title="Details" */%}}
|
||||
{{</* details title="Details" */>}}
|
||||
|
||||
This is the content of the details.
|
||||
|
||||
Markdown is **supported**.
|
||||
|
||||
{{%/* /details */%}}
|
||||
{{</* /details */>}}
|
||||
````
|
||||
|
||||
````markdown
|
||||
{{%/* details title="Click me to reveal" closed="true" */%}}
|
||||
{{</* details title="Click me to reveal" closed="true" */>}}
|
||||
|
||||
This will be hidden by default.
|
||||
|
||||
{{%/* /details */%}}
|
||||
{{</* /details */>}}
|
||||
````
|
||||
|
||||
@@ -8,36 +8,36 @@ title: 详情
|
||||
|
||||
## 示例
|
||||
|
||||
{{% details title="详情" %}}
|
||||
{{< details title="详情" >}}
|
||||
|
||||
这是详情的内容。
|
||||
|
||||
支持 **Markdown** 格式。
|
||||
|
||||
{{% /details %}}
|
||||
{{< /details >}}
|
||||
|
||||
{{% details title="点击我展开" closed="true" %}}
|
||||
{{< details title="点击我展开" closed="true" >}}
|
||||
|
||||
默认情况下,这部分内容会被隐藏。
|
||||
|
||||
{{% /details %}}
|
||||
{{< /details >}}
|
||||
|
||||
## 使用方法
|
||||
|
||||
````markdown
|
||||
{{%/* details title="详情" */%}}
|
||||
{{</* details title="详情" */>}}
|
||||
|
||||
这是详情的内容。
|
||||
|
||||
支持 **Markdown** 格式。
|
||||
|
||||
{{%/* /details */%}}
|
||||
{{</* /details */>}}
|
||||
````
|
||||
|
||||
````markdown
|
||||
{{%/* details title="点击我展开" closed="true" */%}}
|
||||
{{</* details title="点击我展开" closed="true" */>}}
|
||||
|
||||
默认情况下,这部分内容会被隐藏。
|
||||
|
||||
{{%/* /details */%}}
|
||||
{{</* /details */>}}
|
||||
````
|
||||
@@ -4,6 +4,8 @@ title: Steps
|
||||
|
||||
A built-in component to display a series of steps.
|
||||
|
||||
You can use the Markdown attribute `{class="no-step-marker"}` to prevent a heading from being counted as a step.
|
||||
|
||||
## Example
|
||||
|
||||
{{% steps %}}
|
||||
@@ -16,6 +18,10 @@ This is the first step.
|
||||
|
||||
This is the second step.
|
||||
|
||||
#### Step subheading {class="no-step-marker"}
|
||||
|
||||
This will not be counted as a step.
|
||||
|
||||
### Step 3
|
||||
|
||||
This is the third step.
|
||||
@@ -43,5 +49,9 @@ This is the first step.
|
||||
|
||||
This is the second step.
|
||||
|
||||
#### Step subheading {class="no-step-marker"}
|
||||
|
||||
This will not be counted as a step.
|
||||
|
||||
{{%/* /steps */%}}
|
||||
```
|
||||
|
||||
@@ -5,12 +5,10 @@ next: /docs/guide/deploy-site
|
||||
|
||||
## Example
|
||||
|
||||
{{< tabs items="macOS,Linux,Windows" >}}
|
||||
|
||||
{{< tab >}}**macOS**: A desktop operating system by Apple.{{< /tab >}}
|
||||
{{< tab >}}**Linux**: An open-source operating system.{{< /tab >}}
|
||||
{{< tab >}}**Windows**: A desktop operating system by Microsoft.{{< /tab >}}
|
||||
|
||||
{{< tabs >}}
|
||||
{{< tab name="JSON" >}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{< /tab >}}
|
||||
{{< tab name="YAML" >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}}
|
||||
{{< tab name="TOML" >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
## Usage
|
||||
@@ -18,37 +16,35 @@ next: /docs/guide/deploy-site
|
||||
### Default
|
||||
|
||||
```
|
||||
{{</* tabs items="JSON,YAML,TOML" */>}}
|
||||
{{</* tabs */>}}
|
||||
|
||||
{{</* tab */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
|
||||
{{</* tab */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
|
||||
{{</* tab */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
|
||||
{{</* tab name="JSON" */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
|
||||
{{</* tab name="YAML" */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
|
||||
{{</* tab name="TOML" */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
|
||||
|
||||
{{</* /tabs */>}}
|
||||
```
|
||||
|
||||
### Specify Selected Index
|
||||
### Specify Selected Tab
|
||||
|
||||
Use `defaultIndex` property to specify the selected tab. The index starts from 0.
|
||||
Use `selected` property to specify the selected tab.
|
||||
|
||||
```
|
||||
{{</* tabs items="JSON,YAML,TOML" defaultIndex="1" */>}}
|
||||
{{</* tabs */>}}
|
||||
|
||||
{{</* tab */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
|
||||
{{</* tab */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
|
||||
{{</* tab */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
|
||||
{{</* tab name="JSON" */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
|
||||
{{</* tab name="YAML" selected=true */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
|
||||
{{</* tab name="TOML" */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
|
||||
|
||||
{{</* /tabs */>}}
|
||||
```
|
||||
|
||||
The `YAML` tab will be selected by default.
|
||||
|
||||
{{< tabs items="JSON,YAML,TOML" defaultIndex="1" >}}
|
||||
|
||||
{{< tab >}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{< /tab >}}
|
||||
{{< tab >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}}
|
||||
{{< tab >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}}
|
||||
|
||||
{{< tabs >}}
|
||||
{{< tab name="JSON" >}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{< /tab >}}
|
||||
{{< tab name="YAML" selected=true >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}}
|
||||
{{< tab name="TOML" >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
@@ -57,9 +53,9 @@ The `YAML` tab will be selected by default.
|
||||
Markdown syntax including code block is also supported:
|
||||
|
||||
````
|
||||
{{</* tabs items="JSON,YAML,TOML" */>}}
|
||||
{{</* tabs */>}}
|
||||
|
||||
{{</* tab */>}}
|
||||
{{</* tab name="JSON" */>}}
|
||||
```json
|
||||
{ "hello": "world" }
|
||||
```
|
||||
@@ -70,21 +66,21 @@ Markdown syntax including code block is also supported:
|
||||
{{</* /tabs */>}}
|
||||
````
|
||||
|
||||
{{< tabs items="JSON,YAML,TOML" >}}
|
||||
{{< tabs >}}
|
||||
|
||||
{{< tab >}}
|
||||
{{< tab name="JSON" >}}
|
||||
```json
|
||||
{ "hello": "world" }
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab >}}
|
||||
{{< tab name="YAML" >}}
|
||||
```yaml
|
||||
hello: world
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab >}}
|
||||
{{< tab name="TOML" >}}
|
||||
```toml
|
||||
hello = "world"
|
||||
```
|
||||
@@ -97,7 +93,7 @@ Markdown syntax including code block is also supported:
|
||||
|
||||
Tabs with the same list of `items` can be synchronized. When enabled, selecting a tab updates all other tabs with the same `items` and remembers the selection across pages.
|
||||
|
||||
Enable globally in your `hugo.yaml` under the `page` section:
|
||||
Enable/disable globally in your `hugo.yaml` under the `page` section:
|
||||
|
||||
```yaml {filename="hugo.yaml"}
|
||||
params:
|
||||
@@ -106,20 +102,33 @@ params:
|
||||
sync: true
|
||||
```
|
||||
|
||||
With this enabled the following two tab blocks will always display the same selected item:
|
||||
Enable/disable per page inside the front matter:
|
||||
|
||||
```yaml {filename="my_page.md"}
|
||||
---
|
||||
title: My page
|
||||
params:
|
||||
tabs:
|
||||
sync: true
|
||||
---
|
||||
|
||||
Example content.
|
||||
```
|
||||
|
||||
With this enabled, the following two tab blocks will always display the same selected item:
|
||||
|
||||
```markdown
|
||||
{{</* tabs items="A,B" */>}}
|
||||
{{</* tabs */>}}
|
||||
|
||||
{{</* tab */>}}A content{{</* /tab */>}}
|
||||
{{</* tab */>}}B content{{</* /tab */>}}
|
||||
{{</* tab name="A" */>}}A content{{</* /tab */>}}
|
||||
{{</* tab name="B" */>}}B content{{</* /tab */>}}
|
||||
|
||||
{{</* /tabs */>}}
|
||||
|
||||
{{</* tabs items="A,B" */>}}
|
||||
{{</* tabs */>}}
|
||||
|
||||
{{</* tab */>}}Second A content{{</* /tab */>}}
|
||||
{{</* tab */>}}Second B content{{</* /tab */>}}
|
||||
{{</* tab name="A" */>}}Second A content{{</* /tab */>}}
|
||||
{{</* tab name="B" */>}}Second B content{{</* /tab */>}}
|
||||
|
||||
{{</* /tabs */>}}
|
||||
```
|
||||
|
||||
@@ -116,7 +116,6 @@ menu:
|
||||
params:
|
||||
type: link
|
||||
icon: beaker
|
||||
parent: versions
|
||||
- identifier: v0.10
|
||||
name: v0.10 ↗
|
||||
url: https://imfing.github.io/hextra/versions/v0.10/
|
||||
|
||||
@@ -715,6 +715,7 @@
|
||||
"msupsub",
|
||||
"mtable",
|
||||
"mtight",
|
||||
"no-step-marker",
|
||||
"not-prose",
|
||||
"nulldelimiter",
|
||||
"op-symbol",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h{{ .Level }}>
|
||||
<h{{ .Level }} {{- with .Attributes.class }} class="{{ . }}" {{- end }}>
|
||||
{{- .Text | safeHTML -}}
|
||||
{{- if gt .Level 1 -}}
|
||||
<span class="hx:absolute hx:-mt-20" id="{{ .Anchor | safeURL }}"></span>
|
||||
|
||||
@@ -16,4 +16,9 @@
|
||||
{{ partial "components/analytics/matomo.html" . }}
|
||||
{{- end }}
|
||||
|
||||
<!-- GoatCounter -->
|
||||
{{- if .Site.Params.analytics.goatCounter -}}
|
||||
{{ partial "components/analytics/goat-counter.html" . }}
|
||||
{{- end -}}
|
||||
|
||||
{{- end }}
|
||||
|
||||
17
layouts/_partials/components/analytics/goat-counter.html
Normal file
17
layouts/_partials/components/analytics/goat-counter.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{{- with .Site.Params.analytics.goatCounter -}}
|
||||
{{- if not .code -}}
|
||||
{{- errorf "Missing GoatCounter 'code' configuration. See https://imfing.github.io/hextra/versions/latest/docs/guide/configuration/#goatcounter-analytics" -}}
|
||||
{{- end -}}
|
||||
|
||||
<script
|
||||
data-goatcounter="https://{{ .code }}.goatcounter.com/count"
|
||||
data-goatcounter-settings='
|
||||
{
|
||||
"no_onload":{{ .noOnload | default false }},
|
||||
"no_events":{{ .noEvents | default false }},
|
||||
"allow_local":{{ .allowLocal | default false }},
|
||||
"allow_frame":{{ .allowFrame | default false }}
|
||||
}
|
||||
'
|
||||
async src="//gc.zgo.at/count.js"></script>
|
||||
{{- end -}}
|
||||
@@ -15,7 +15,7 @@ https://umami.is/docs/tracker-configuration
|
||||
|
||||
{{- $attributes := newScratch -}}
|
||||
|
||||
{{- $attributes.SetInMap "umami" "src" (printf "%s/%s" .serverURL (.scriptName | default "umami.js")) -}}
|
||||
{{- $attributes.SetInMap "umami" "src" (printf "%s/%s" .serverURL (.scriptName | default "script.js")) -}}
|
||||
{{- $attributes.SetInMap "umami" "data-website-id" .websiteID -}}
|
||||
|
||||
{{- if .hostURL -}}
|
||||
|
||||
@@ -55,32 +55,17 @@
|
||||
|
||||
{{ partial "components/analytics/analytics.html" . }}
|
||||
|
||||
<script>
|
||||
// The section must not be in the theme.js file because it can create a quick flash (switch between light and dark).
|
||||
{{- $scriptsHead := slice -}}
|
||||
{{- range resources.Match "js/head/*.js" -}}
|
||||
{{ $scriptsHead = $scriptsHead | append (resources.ExecuteAsTemplate .Name $ .) }}
|
||||
{{- end -}}
|
||||
|
||||
function setTheme(theme) {
|
||||
document.documentElement.classList.remove("light", "dark");
|
||||
{{- $scripts := $scriptsHead | resources.Concat "js/main-head.js" -}}
|
||||
|
||||
if (theme !== "light" && theme !== "dark") {
|
||||
theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
document.documentElement.classList.add(theme);
|
||||
document.documentElement.style.colorScheme = theme;
|
||||
}
|
||||
|
||||
setTheme("color-theme" in localStorage ? localStorage.getItem("color-theme") : '{{ site.Params.theme.default | default `system`}}')
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// The section must not be in the banner.js file because it can create a quick flash.
|
||||
|
||||
if (localStorage.getItem('{{ site.Params.banner.key | default `banner-closed` }}')) {
|
||||
document.documentElement.style.setProperty("--hextra-banner-height", "0px");
|
||||
document.documentElement.classList.add("hextra-banner-hidden");
|
||||
}
|
||||
</script>
|
||||
{{- if hugo.IsProduction -}}
|
||||
{{- $scripts = $scripts | minify | fingerprint -}}
|
||||
{{- end -}}
|
||||
<script src="{{ $scripts.RelPermalink }}" integrity="{{ $scripts.Data.Integrity }}"></script>
|
||||
|
||||
<!-- Math engine -->
|
||||
{{ $noop := .WordCount -}}
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
{{- $jsSwitcherMenu := resources.Get "js/switcher-menu.js" -}}
|
||||
{{- $jsTheme := resources.Get "js/theme.js" | resources.ExecuteAsTemplate "theme.js" . -}}
|
||||
{{- $jsBanner := resources.Get "js/banner.js" | resources.ExecuteAsTemplate "banner.js" . -}}
|
||||
{{- $jsMenu := resources.Get "js/menu.js" -}}
|
||||
{{- $jsTabs := resources.Get "js/tabs.js" -}}
|
||||
{{- $jsLang := resources.Get "js/lang.js" -}}
|
||||
{{- $jsNavMenu := resources.Get "js/nav-menu.js" -}}
|
||||
{{- $jsCodeCopy := resources.Get "js/code-copy.js" -}}
|
||||
{{- $jsFileTree := resources.Get "js/filetree.js" -}}
|
||||
{{- $jsSidebar := resources.Get "js/sidebar.js" -}}
|
||||
{{- $jsBackToTop := resources.Get "js/back-to-top.js" -}}
|
||||
{{- $jsTocScroll := resources.Get "js/toc-scroll.js" -}}
|
||||
{{- $jsFavicon := resources.Get "js/favicon.js" | resources.ExecuteAsTemplate "favicon.js" . -}}
|
||||
{{- $scriptsBody := slice }}
|
||||
{{- range resources.Match "js/core/*.js" -}}
|
||||
{{ $scriptsBody = $scriptsBody | append (resources.ExecuteAsTemplate .Name $ .) }}
|
||||
{{- end -}}
|
||||
|
||||
{{- $scripts := slice $jsSwitcherMenu $jsTheme $jsBanner $jsMenu $jsCodeCopy $jsTabs $jsLang $jsNavMenu $jsFileTree $jsSidebar $jsBackToTop $jsTocScroll $jsFavicon | resources.Concat "js/main.js" -}}
|
||||
{{- $scripts := $scriptsBody | resources.Concat "js/main.js" -}}
|
||||
{{- if hugo.IsProduction -}}
|
||||
{{- $scripts = $scripts | minify | fingerprint -}}
|
||||
{{- end -}}
|
||||
|
||||
69
layouts/_partials/shortcodes/tabs.html
Normal file
69
layouts/_partials/shortcodes/tabs.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{{- $tabsID := .id }}
|
||||
|
||||
{{- /*
|
||||
The `tabs` parameter is a list of dict with the following keys:
|
||||
- `id`: (int) the ID of the tab (the Ordinal of the tab shortcode).
|
||||
- `name`: (string) the name of the tab (the title).
|
||||
- `content`: (string) the content of the tab.
|
||||
- `selected`: (bool) whether the tab is selected.
|
||||
*/ -}}
|
||||
{{- $tabs := .tabs }}
|
||||
|
||||
{{- if eq (len $tabs) 0 -}}
|
||||
{{ errorf "tabs must have at least one tab" }}
|
||||
{{- end -}}
|
||||
|
||||
{{- $enableSync := .enableSync }}
|
||||
|
||||
{{- /* Create group data for syncing and select the first tab if none is selected. */ -}}
|
||||
{{- $selectedIndex := 0 -}}
|
||||
{{ $dataTabGroup := slice -}}
|
||||
|
||||
{{- range $i, $item := $tabs -}}
|
||||
{{- $dataTabGroup = $dataTabGroup | append ($item.name) -}}
|
||||
|
||||
{{- if $item.selected -}}
|
||||
{{- $selectedIndex = $i -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Generate a unique ID for each tab group. */ -}}
|
||||
{{- $globalID := printf "tabs-%02v" $tabsID -}}
|
||||
|
||||
<div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain">
|
||||
<div
|
||||
class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800"
|
||||
{{ if $enableSync }} data-tab-group="{{ delimit $dataTabGroup `,` }}"{{ end }}
|
||||
>
|
||||
{{- range $i, $item := $tabs -}}
|
||||
<button
|
||||
class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white"
|
||||
role="tab"
|
||||
type="button"
|
||||
aria-controls="tabs-panel-{{ $globalID }}-{{ $item.id }}"
|
||||
{{- if eq $i $selectedIndex -}}
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
data-state="selected"
|
||||
{{- end }}
|
||||
>
|
||||
{{- $item.name -}}
|
||||
</button>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{- range $i, $item := $tabs -}}
|
||||
<div
|
||||
class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block"
|
||||
id="tabs-panel-{{ $globalID }}-{{ $item.id }}"
|
||||
role="tabpanel"
|
||||
{{- if eq $i $selectedIndex -}}
|
||||
tabindex="0"
|
||||
data-state="selected"
|
||||
{{ end -}}
|
||||
>
|
||||
{{- $item.content | markdownify -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
@@ -3,17 +3,22 @@
|
||||
{{- $disableSidebar := .disableSidebar | default false -}}
|
||||
{{- $displayPlaceholder := .displayPlaceholder | default false -}}
|
||||
|
||||
{{- $sidebarClass := cond $disableSidebar (cond $displayPlaceholder "hx:md:hidden hx:xl:block" "hx:md:hidden") "hx:md:sticky" -}}
|
||||
|
||||
{{- $navRoot := cond (eq site.Home.Type "docs") site.Home $context.FirstSection -}}
|
||||
{{- $pageURL := $context.RelPermalink -}}
|
||||
|
||||
{{/* EXPERIMENTAL */}}
|
||||
{{- if .context.Params.sidebar.hide -}}
|
||||
{{- $disableSidebar = true -}}
|
||||
{{- $displayPlaceholder = true -}}
|
||||
{{- $displayPlaceholder = false -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $sidebarClass := "hx:md:sticky" -}}
|
||||
{{- if $disableSidebar -}}
|
||||
{{- if $displayPlaceholder -}}
|
||||
{{- $sidebarClass = "hx:md:hidden hx:xl:block" -}}
|
||||
{{- else -}}
|
||||
{{- $sidebarClass = "hx:md:hidden" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<aside class="hextra-sidebar-container hx:flex hx:flex-col hx:print:hidden hx:md:top-16 hx:md:shrink-0 hx:md:w-64 hx:md:self-start hx:max-md:[transform:translate3d(0,-100%,0)] {{ $sidebarClass }}">
|
||||
<!-- Search bar on small screen -->
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
{{- /*
|
||||
Create a tab.
|
||||
|
||||
@example {{< tab >}}content{{< /tab >}}
|
||||
@param {string} name The name of the tab.
|
||||
@param {string} selected Whether the tab is selected.
|
||||
|
||||
@example {{< tab name="Foo" selected=true >}}content{{< /tab >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $defaultIndex := int ((.Parent.Get "defaultIndex") | default "0") -}}
|
||||
{{- $name := .Get "name" | default (printf "Tab %d" .Ordinal) -}}
|
||||
|
||||
<div
|
||||
class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block"
|
||||
id="tabs-panel-{{ .Ordinal }}"
|
||||
role="tabpanel"
|
||||
{{- if eq .Ordinal $defaultIndex }} tabindex="0" {{ end -}}
|
||||
{{- if eq .Ordinal $defaultIndex }} data-state="selected" {{ end -}}
|
||||
>
|
||||
{{- .InnerDeindent | markdownify -}}
|
||||
</div>
|
||||
{{- /* Drop trailing newlines */ -}}
|
||||
{{- $selected := .Get "selected" -}}
|
||||
{{- if .Parent.Get "defaultIndex" -}}
|
||||
{{- $selected = eq .Ordinal (int (.Parent.Get "defaultIndex")) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $tabs := .Parent.Store.Get "tabs" | default slice -}}
|
||||
{{ .Parent.Store.Set "tabs" ($tabs | append (dict
|
||||
"id" .Ordinal
|
||||
"name" $name
|
||||
"content" .InnerDeindent
|
||||
"selected" $selected
|
||||
))
|
||||
-}}
|
||||
|
||||
@@ -1,49 +1,39 @@
|
||||
{{- /*
|
||||
Create a tabbed interface with the given items.
|
||||
|
||||
@param {string} items The items to display in the tabs.
|
||||
@param {string} defaultIndex The index of the default tab.
|
||||
|
||||
@example {{< tabs items="JSON,YAML,TOML" >}}{{< /tabs >}}
|
||||
@example {{< tabs >}}...{{< /tabs >}}
|
||||
*/ -}}
|
||||
|
||||
{{- $items := split (.Get "items") "," -}}
|
||||
{{- $defaultIndex := int ((.Get "defaultIndex") | default "0") -}}
|
||||
{{- /* Unused, but required for the shortcode to work. */ -}}
|
||||
{{- .Inner -}}
|
||||
|
||||
{{- $enableSync := site.Params.page.tabs.sync | default false -}}
|
||||
|
||||
{{- if not (.Get "items") -}}
|
||||
{{ errorf "tabs shortcode: 'items' parameter is required" }}
|
||||
{{- /* Enable syncing of tabs across the page. */ -}}
|
||||
{{- $enableSync := false -}}
|
||||
{{- if or (eq .Page.Params.tabs.sync false) (eq .Page.Params.tabs.sync true) -}}
|
||||
{{- $enableSync = .Page.Params.tabs.sync -}}
|
||||
{{- else -}}
|
||||
{{- $enableSync = site.Params.page.tabs.sync | default false -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if not $items -}}
|
||||
{{ errorf "tabs shortcode: 'items' parameter cannot be empty" }}
|
||||
{{- $tabs := ($.Store.Get "tabs") | default slice -}}
|
||||
|
||||
{{- /* Compatibility with previous parameter "items". */ -}}
|
||||
{{- if .Get "defaultIndex" -}}
|
||||
{{- warnf "The 'defaultIndex' parameter of the 'tabs' shortcode is deprecated. Please use 'selected' on 'tab' instead." -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- range $items -}}
|
||||
{{- if eq (trim . " ") "" -}}
|
||||
{{ errorf "tabs shortcode: empty item found in 'items' parameter" }}
|
||||
{{- if .Get "items" -}}
|
||||
{{- warnf "The 'items' parameter of the 'tabs' shortcode is deprecated. Please use 'name' on 'tab' instead." -}}
|
||||
|
||||
{{- $items := split (.Get "items") "," -}}
|
||||
|
||||
{{- $temp := slice -}}
|
||||
{{- range $i, $item := $items -}}
|
||||
{{- $tab := index $tabs $i -}}
|
||||
{{- $temp = $temp | append (merge $tab (dict "name" $item)) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $tabs = $temp -}}
|
||||
{{- end -}}
|
||||
|
||||
<div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain">
|
||||
<div class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800"{{ if $enableSync }} data-tab-group="{{ delimit $items `,` }}"{{ end }}>
|
||||
{{- range $i, $item := $items -}}
|
||||
<button
|
||||
class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white"
|
||||
role="tab"
|
||||
type="button"
|
||||
aria-controls="tabs-panel-{{ $i }}"
|
||||
{{- if eq $i $defaultIndex }} aria-selected="true" {{ end -}}
|
||||
{{- if eq $i $defaultIndex }} tabindex="0" {{ end -}}
|
||||
{{- if eq $i $defaultIndex }} data-state="selected"{{ end -}}
|
||||
>
|
||||
{{- $item -}}
|
||||
</button>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{- .Inner -}}
|
||||
</div>
|
||||
{{- /* Drop trailing newlines */ -}}
|
||||
{{- partial "shortcodes/tabs" (dict "tabs" $tabs "enableSync" $enableSync "id" .Ordinal) -}}
|
||||
|
||||
Reference in New Issue
Block a user