Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ Activate the formats in the `[outputs]` section of your `hugo.toml`. Include `ll
[outputs]
home = ["HTML", "llmstxt", "markdown"] # add "llmscomponents" for documentation sites
page = ["HTML", "markdown"]
section = ["HTML", "markdown"]
```

> [!NOTE]
> If your site already defines `[outputs]`, extend the existing lists rather than replacing them. The `HTML` output must remain to keep the regular site building correctly.
> If your site already defines `[outputs]`, extend the existing lists rather than replacing them. The `HTML` output must remain to keep the regular site building correctly. The `section` key ensures section index pages (`_index.md`) get a Markdown equivalent and are linked from `llms.txt`.

## Contributing

Expand Down
1 change: 1 addition & 0 deletions exampleSite/hugo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ title = 'Test site for mod-llm'
[outputs]
home = ["HTML", "llmstxt", "llmscomponents", "markdown"]
page = ["HTML", "markdown"]
section = ["HTML", "markdown"]

[module]
# Build and serve using local mod-llm clone declared in the named Hugo workspace:
Expand Down
4 changes: 4 additions & 0 deletions i18n/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
translation: Artikel
- id: llm-block-cards
translation: Karten
- id: llm-block-contact-form
translation: Kontaktformular
- id: llm-block-cta
translation: Handlungsaufruf
- id: llm-block-faq
translation: FAQ
- id: llm-block-heading
translation: Überschrift
- id: llm-block-featured
translation: Empfohlen
- id: llm-block-hero
Expand Down
4 changes: 4 additions & 0 deletions i18n/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
translation: Articles
- id: llm-block-cards
translation: Cards
- id: llm-block-contact-form
translation: Contact Form
- id: llm-block-cta
translation: Call to Action
- id: llm-block-faq
translation: FAQ
- id: llm-block-heading
translation: Heading
- id: llm-block-featured
translation: Featured
- id: llm-block-hero
Expand Down
4 changes: 4 additions & 0 deletions i18n/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
translation: Articles
- id: llm-block-cards
translation: Cartes
- id: llm-block-contact-form
translation: Formulaire de contact
- id: llm-block-cta
translation: Appel à l'action
- id: llm-block-faq
translation: FAQ
- id: llm-block-heading
translation: En-tête
- id: llm-block-featured
translation: En vedette
- id: llm-block-hero
Expand Down
4 changes: 4 additions & 0 deletions i18n/nl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
translation: Artikelen
- id: llm-block-cards
translation: Kaarten
- id: llm-block-contact-form
translation: Contactformulier
- id: llm-block-cta
translation: Oproep tot actie
- id: llm-block-faq
translation: FAQ
- id: llm-block-heading
translation: Koptekst
- id: llm-block-featured
translation: Uitgelicht
- id: llm-block-hero
Expand Down
4 changes: 4 additions & 0 deletions i18n/pl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
translation: Artykuły
- id: llm-block-cards
translation: Karty
- id: llm-block-contact-form
translation: Formularz kontaktowy
- id: llm-block-cta
translation: Wezwanie do działania
- id: llm-block-faq
translation: FAQ
- id: llm-block-heading
translation: Nagłówek
- id: llm-block-featured
translation: Wyróżnione
- id: llm-block-hero
Expand Down
4 changes: 4 additions & 0 deletions i18n/pt-br.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
translation: Artigos
- id: llm-block-cards
translation: Cartões
- id: llm-block-contact-form
translation: Formulário de contato
- id: llm-block-cta
translation: Chamada para ação
- id: llm-block-faq
translation: FAQ
- id: llm-block-heading
translation: Cabeçalho
- id: llm-block-featured
translation: Destaque
- id: llm-block-hero
Expand Down
4 changes: 4 additions & 0 deletions i18n/zh-hans.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
translation: 文章
- id: llm-block-cards
translation: 卡片
- id: llm-block-contact-form
translation: 联系表单
- id: llm-block-cta
translation: 行动号召
- id: llm-block-faq
translation: 常见问题
- id: llm-block-heading
translation: 标题
- id: llm-block-featured
translation: 精选
- id: llm-block-hero
Expand Down
4 changes: 4 additions & 0 deletions i18n/zh-hant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
translation: 文章
- id: llm-block-cards
translation: 卡片
- id: llm-block-contact-form
translation: 聯絡表單
- id: llm-block-cta
translation: 行動號召
- id: llm-block-faq
translation: 常見問題
- id: llm-block-heading
translation: 標題
- id: llm-block-featured
translation: 精選
- id: llm-block-hero
Expand Down
71 changes: 60 additions & 11 deletions layouts/index.llmstxt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,87 @@

> {{ with $site.Params.main.description }}{{ . }}{{ else }}{{ $site.Title }}{{ end }}

{{- /* Collect pages grouped by section, default language only, skip excluded */ -}}
{{- /*
Collect all pages into an ordered map of section → page list.
$sections: dict mapping section key → slice of pages
$sectionOrder: slice of section keys in encounter order (controls output order)
*/ -}}
{{- $sections := dict -}}
{{- $sectionOrder := slice -}}
{{- /* Prepend homepage to the pages section */ -}}

{{- /* Homepage is always first in the Pages section, unless excluded or missing markdown output */ -}}
{{- if and (not $site.Home.Params.llm.exclude) ($site.Home.OutputFormats.Get "markdown") -}}
{{- $sectionOrder = $sectionOrder | append "pages" -}}
{{- $sections = merge $sections (dict "pages" (slice $site.Home)) -}}
{{- end -}}
{{- range $site.RegularPages -}}
{{- if and (eq .Language.Lang $site.Language.Lang) (not .Params.llm.exclude) -}}
{{- $section := .Section | default "pages" -}}
{{- if not (index $sections $section) -}}
{{- $sectionOrder = $sectionOrder | append $section -}}

{{- /*
Iterate over all regular pages and section index pages.
RegularPages first so sections can check whether child pages already registered them.
Headless bundles (.RelPermalink == "") and llm.exclude pages are skipped.
*/ -}}
{{- $allPages := $site.RegularPages | append $site.Sections -}}
{{- range $allPages -}}
{{- if and (eq .Language.Lang $site.Language.Lang) (not .Params.llm.exclude) .RelPermalink -}}
{{- if eq .Kind "section" -}}
{{- $hasChildren := or (len .RegularPages) (len .Sections) -}}
{{- if not $hasChildren -}}
{{- /*
Standalone section index (no child pages) — treat as a regular content page
and place it under the flat Pages section rather than its own heading.
Example: /pricing/, /data-catalog/, /releases/
*/ -}}
{{- if not (index $sections "pages") -}}
{{- $sectionOrder = $sectionOrder | append "pages" -}}
{{- end -}}
{{- $sectionPages := index $sections "pages" | default slice -}}
{{- $sections = merge $sections (dict "pages" ($sectionPages | append .)) -}}
{{- else -}}
{{- /*
Section index with child pages — register the section if not yet seen, then
prepend the index page so it leads the section list.
Skip the index page entirely if its title matches the section key (e.g. "Insights"
under ## Insights), since the heading already communicates the same information.
*/ -}}
{{- $section := .Section | default "pages" -}}
{{- if not (index $sections $section) -}}
{{- $sectionOrder = $sectionOrder | append $section -}}
{{- $sections = merge $sections (dict $section (slice)) -}}
{{- end -}}
{{- if ne (.Title | lower) $section -}}
{{- $sectionPages := index $sections $section | default slice -}}
{{- $sections = merge $sections (dict $section (slice . | append $sectionPages)) -}}
{{- end -}}
{{- end -}}
{{- else -}}
{{- /* Regular page — append to its section, registering the section on first encounter */ -}}
{{- $section := .Section | default "pages" -}}
{{- if not (index $sections $section) -}}
{{- $sectionOrder = $sectionOrder | append $section -}}
{{- end -}}
{{- $sectionPages := index $sections $section | default slice -}}
{{- $sections = merge $sections (dict $section ($sectionPages | append .)) -}}
{{- end -}}
{{- $sectionPages := index $sections $section | default slice -}}
{{- $sections = merge $sections (dict $section ($sectionPages | append .)) -}}
{{- end -}}
{{- end -}}

{{- /* Render each section as a level-2 heading followed by its page list */ -}}
{{- range $section := $sectionOrder }}

## {{ $section | title }}

{{ range $pages := index $sections $section -}}
{{- /* Prefer explicit llm.description, then front matter description, then truncated summary */ -}}
{{- $desc := or .Params.llm.description .Description (.Summary | plainify | truncate 120) -}}
{{- $hasMarkdown := .OutputFormats.Get "markdown" -}}
{{- $link := .Permalink -}}
{{- if $hasMarkdown }}{{- $link = printf "%sindex.md" .Permalink -}}{{ end -}}
{{- /* Use site title for the homepage — the page title is typically a marketing tagline */ -}}
{{- $title := cond .IsHome $site.Title .Title -}}
{{- if $desc -}}
{{- printf "- [%s](%s): %s\n" .Title $link $desc -}}
{{- printf "- [%s](%s): %s\n" $title $link $desc -}}
{{- else -}}
{{- printf "- [%s](%s)\n" .Title $link -}}
{{- printf "- [%s](%s)\n" $title $link -}}
{{- end -}}
{{- end -}}
{{- end -}}
Expand Down