From 1722e573fb777edd8bfb08c875bf810126234c02 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Fri, 14 Aug 2026 09:13:00 +0200 Subject: [PATCH 1/8] feat: configurable top nav via section: grouping in navigation_preview.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a config-level section: grouping to navigation_preview.yml that maps the 11 flat top-level toc: entries to 5 named tabs in the top nav bar. Sections are config-only — children are added to the site tree as flat roots, so no page URLs change. New ISiteNavigationEntry interface and SiteSectionRef record allow the YAML converter to parse both toc: and section: entries. SiteTableOfContents becomes List. SiteNavigation iterates entries and descends into section children when encountered. SectionTopNavBuilder derives TopNavRenderModel from the nav file's entry list. Section tabs carry SectionIds so active state is resolved by comparing the current page's NavigationRoot.Id to the tab's set. BuildContext.TopNav wires the render model through to every page. _SecondaryNav.cshtml renders the tabs and delegates dropdown/external behaviour to secondary-nav.ts. Section structure in navigation_preview.yml: - section: Guides (get-started, solutions, manage-data, explore-analyze, deploy-manage, cloud-account) - section: Troubleshoot (troubleshoot) - section: Release notes (release-notes/intro + release children) - section: Reference (reference + reference children) - section: Extend (extend, contribute-docs) Co-Authored-By: Claude Sonnet 4.6 --- config/navigation_preview.yml | 1209 ++++++++--------- .../BuildContext.cs | 6 + .../Toc/SiteNavigationFile.cs | 163 ++- .../Toc/TopNavigation.cs | 55 + .../Assembler/SiteNavigation.cs | 29 +- .../INavigationTraversable.cs | 10 +- src/Elastic.Documentation.Site/Assets/main.ts | 4 +- .../Assets/secondary-nav-dropdown.css | 87 ++ .../Assets/secondary-nav.test.ts | 73 + .../Assets/secondary-nav.ts | 45 + .../Assets/styles.css | 1 + .../Layout/_SecondaryNav.cshtml | 130 +- src/Elastic.Documentation.Site/_ViewModels.cs | 6 + src/Elastic.Markdown/HtmlWriter.cs | 1 + src/Elastic.Markdown/Page/Index.cshtml | 1 + src/Elastic.Markdown/Page/IndexViewModel.cs | 3 + .../AssembleSources.cs | 20 + .../Building/AssemblerBuildService.cs | 4 + .../Navigation/GlobalNavigationHtmlWriter.cs | 2 +- .../Navigation/LlmsNavigationEnhancer.cs | 8 +- .../Navigation/SectionTopNavBuilder.cs | 88 ++ .../SiteNavigationFileTests.cs | 113 +- .../Rendering/SecondaryNavRenderingTests.cs | 180 +++ 23 files changed, 1491 insertions(+), 747 deletions(-) create mode 100644 src/Elastic.Documentation.Configuration/Toc/TopNavigation.cs create mode 100644 src/Elastic.Documentation.Site/Assets/secondary-nav-dropdown.css create mode 100644 src/Elastic.Documentation.Site/Assets/secondary-nav.test.ts create mode 100644 src/Elastic.Documentation.Site/Assets/secondary-nav.ts create mode 100644 src/services/Elastic.Documentation.Assembler/Navigation/SectionTopNavBuilder.cs create mode 100644 tests/Navigation.Tests/Rendering/SecondaryNavRenderingTests.cs diff --git a/config/navigation_preview.yml b/config/navigation_preview.yml index c3b093b716..9407e1d4b9 100644 --- a/config/navigation_preview.yml +++ b/config/navigation_preview.yml @@ -3,6 +3,7 @@ # and content sources for the documentation site. ############################################# + # Use sparingly, makes assembler aware of toc container folders # That are not linked in the global toc but all the children toc they define are. phantoms: @@ -14,700 +15,640 @@ phantoms: - toc: cloud://release-notes toc: - ############# - # NARRATIVE # - ############# - - toc: get-started - - toc: solutions - - toc: manage-data - - toc: explore-analyze - - toc: deploy-manage - - toc: cloud-account - - toc: troubleshoot - + ########## + # GUIDES # + ########## + - section: Guides + children: + - toc: get-started + - toc: solutions + - toc: manage-data + - toc: explore-analyze + - toc: deploy-manage + - toc: cloud-account + + ############### + # TROUBLESHOOT # + ############### + - section: Troubleshoot + children: + - toc: troubleshoot ################# # RELEASE NOTES # ################# - - toc: docs-content://release-notes/intro - path_prefix: release-notes + - section: Release notes children: - # Elasticsearch - # https://github.com/elastic/elasticsearch/blob/main/docs/release-notes/toc.yml - - toc: elasticsearch://release-notes - path_prefix: release-notes/elasticsearch - children: - # Elasticsearch Clients - # https://github.com/elastic/elasticsearch-java/blob/main/docs/release-notes/toc.yml - - toc: elasticsearch-java://release-notes - path_prefix: release-notes/elasticsearch/clients/java - # https://github.com/elastic/elasticsearch-js/blob/main/docs/release-notes/toc.yml - - toc: elasticsearch-js://release-notes - path_prefix: release-notes/elasticsearch/clients/javascript - # https://github.com/elastic/elasticsearch-net/blob/main/docs/release-notes/toc.yml - - toc: elasticsearch-net://release-notes - path_prefix: release-notes/elasticsearch/clients/dotnet - # https://github.com/elastic/elasticsearch-php/blob/main/docs/release-notes/toc.yml - - toc: elasticsearch-php://release-notes - path_prefix: release-notes/elasticsearch/clients/php - # https://github.com/elastic/elasticsearch-py/blob/main/docs/release-notes/toc.yml - - toc: elasticsearch-py://release-notes - path_prefix: release-notes/elasticsearch/clients/python - # https://github.com/elastic/elasticsearch-ruby/blob/main/docs/release-notes/toc.yml - - toc: elasticsearch-ruby://release-notes - path_prefix: release-notes/elasticsearch/clients/ruby - # Elasticsearch Hadoop - # https://github.com/elastic/elasticsearch-hadoop/blob/main/docs/release-notes/toc.yml - - toc: elasticsearch-hadoop://release-notes - path_prefix: release-notes/elasticsearch-hadoop - - # Kibana - # https://github.com/elastic/kibana/blob/main/docs/release-notes/toc.yml - - toc: kibana://release-notes - path_prefix: release-notes/kibana - - # Elastic Agent - - toc: elastic-agent://release-notes - path_prefix: release-notes/elastic-agent - - # Fleet Server - - toc: fleet-server://release-notes - path_prefix: release-notes/fleet-server - - # Logstash - # https://github.com/elastic/logstash/blob/main/docs/release-notes/toc.yml - - toc: logstash://release-notes - path_prefix: release-notes/logstash - - # Beats - # https://github.com/elastic/beats/blob/main/docs/release-notes/toc.yml - - toc: beats://release-notes - path_prefix: release-notes/beats - - # Serverless - # Comes from docs-content - # https://github.com/elastic/docs-content/blob/main/release-notes/elastic-cloud-serverless/toc.yml - - toc: docs-content://release-notes/elastic-cloud-serverless - path_prefix: release-notes/cloud-serverless - - # Cloud Hosted - # https://github.com/elastic/cloud/blob/master/docs/release-notes/cloud-hosted/toc.yml - # We probably need to add `max_toc_depth: 2` to - # https://github.com/elastic/cloud/blob/master/docs/docset.yml - - toc: cloud://release-notes/cloud-hosted - path_prefix: release-notes/cloud-hosted - - # Cloud Enterprise - # https://github.com/elastic/cloud/blob/master/docs/release-notes/cloud-enterprise/toc.yml - - toc: cloud://release-notes/cloud-enterprise - path_prefix: release-notes/cloud-enterprise - - # K8s - # https://github.com/elastic/cloud-on-k8s/blob/main/docs/release-notes/toc.yml - - toc: cloud-on-k8s://release-notes - path_prefix: release-notes/cloud-on-k8s - - # Observability - # https://github.com/elastic/docs-content/blob/main/release-notes/elastic-observability/toc.yml - # Comes from docs-content - - toc: docs-content://release-notes/elastic-observability - path_prefix: release-notes/observability + - toc: docs-content://release-notes/intro + path_prefix: release-notes children: - # EDOT Android - # https://github.com/elastic/apm-agent-android/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-android://release-notes - path_prefix: release-notes/edot/sdks/android - # Elastic Cloud Forwarder for AWS - # https://github.com/elastic/edot-cloud-forwarder-aws/blob/main/docs/release-notes/toc.yml - - toc: edot-cloud-forwarder-aws://release-notes - path_prefix: release-notes/edot/cloud-forwarder/aws - # EDOT iOS / Swift - # https://github.com/elastic/apm-agent-ios/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-ios://release-notes - path_prefix: release-notes/edot/sdks/ios - # EDOT Java - # https://github.com/elastic/elastic-otel-java/blob/main/docs/release-notes/toc.yml - - toc: elastic-otel-java://release-notes - path_prefix: release-notes/edot/sdks/java - # EDOT .NET - # https://github.com/elastic/elastic-otel-dotnet/blob/main/docs/release-notes/toc.yml - - toc: elastic-otel-dotnet://release-notes - path_prefix: release-notes/edot/sdks/dotnet - # EDOT Node - # https://github.com/elastic/elastic-otel-node/blob/main/docs/release-notes/toc.yml - - toc: elastic-otel-node://release-notes - path_prefix: release-notes/edot/sdks/node - # EDOT Python - # https://github.com/elastic/elastic-otel-python/blob/main/docs/release-notes/toc.yml - - toc: elastic-otel-python://release-notes - path_prefix: release-notes/edot/sdks/python - # EDOT PHP - # https://github.com/elastic/elastic-otel-php/blob/main/docs/release-notes/toc.yml - - toc: elastic-otel-php://release-notes - path_prefix: release-notes/edot/sdks/php - # EDOT Browser (RUM) - # https://github.com/elastic/elastic-otel-rum-js/blob/main/docs/release-notes/toc.yml - - toc: elastic-otel-rum-js://release-notes - path_prefix: release-notes/edot/sdks/rum - # APM - # https://github.com/elastic/docs-content/blob/main/release-notes/elastic-apm/toc.yml - - toc: apm-server://release-notes - path_prefix: release-notes/apm - # APM .NET agent - # https://github.com/elastic/apm-agent-dotnet/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-dotnet://release-notes - path_prefix: release-notes/apm/agents/dotnet - # APM Go agent - # https://github.com/elastic/apm-agent-go/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-go://release-notes - path_prefix: release-notes/apm/agents/go - # APM Java agent - # https://github.com/elastic/apm-agent-java/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-java://release-notes - path_prefix: release-notes/apm/agents/java - # APM Node.js agent - # https://github.com/elastic/apm-agent-nodejs/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-nodejs://release-notes - path_prefix: release-notes/apm/agents/nodejs - # APM PHP agent - # https://github.com/elastic/apm-agent-php/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-php://release-notes - path_prefix: release-notes/apm/agents/php - # APM Python agent - # https://github.com/elastic/apm-agent-python/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-python://release-notes - path_prefix: release-notes/apm/agents/python - # APM Ruby agent - # https://github.com/elastic/apm-agent-ruby/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-ruby://release-notes - path_prefix: release-notes/apm/agents/ruby - # APM RUM JavaScript Agent - # https://github.com/elastic/apm-agent-rum-js/blob/main/docs/release-notes/toc.yml - - toc: apm-agent-rum-js://release-notes - path_prefix: release-notes/apm/agents/rum-js - # APM AWS Lambda Extension - # https://github.com/elastic/apm-aws-lambda/blob/main/docs/release-notes/toc.yml - - toc: apm-aws-lambda://release-notes - path_prefix: release-notes/apm/aws-lambda - - # Security - # https://github.com/elastic/docs-content/blob/main/release-notes/elastic-security/toc.yml - - toc: docs-content://release-notes/elastic-security - path_prefix: release-notes/security - - # ECS - # https://github.com/elastic/ecs/blob/main/docs/release-notes/toc.yml - - toc: ecs://release-notes - path_prefix: release-notes/ecs - - # ECCTL - # https://github.com/elastic/ecctl/blob/master/docs/release-notes/toc.yml - - toc: ecctl://release-notes - path_prefix: release-notes/ecctl + # Elasticsearch + # https://github.com/elastic/elasticsearch/blob/main/docs/release-notes/toc.yml + - toc: elasticsearch://release-notes + path_prefix: release-notes/elasticsearch + children: + # Elasticsearch Clients + # https://github.com/elastic/elasticsearch-java/blob/main/docs/release-notes/toc.yml + - toc: elasticsearch-java://release-notes + path_prefix: release-notes/elasticsearch/clients/java + # https://github.com/elastic/elasticsearch-js/blob/main/docs/release-notes/toc.yml + - toc: elasticsearch-js://release-notes + path_prefix: release-notes/elasticsearch/clients/javascript + # https://github.com/elastic/elasticsearch-net/blob/main/docs/release-notes/toc.yml + - toc: elasticsearch-net://release-notes + path_prefix: release-notes/elasticsearch/clients/dotnet + # https://github.com/elastic/elasticsearch-php/blob/main/docs/release-notes/toc.yml + - toc: elasticsearch-php://release-notes + path_prefix: release-notes/elasticsearch/clients/php + # https://github.com/elastic/elasticsearch-py/blob/main/docs/release-notes/toc.yml + - toc: elasticsearch-py://release-notes + path_prefix: release-notes/elasticsearch/clients/python + # https://github.com/elastic/elasticsearch-ruby/blob/main/docs/release-notes/toc.yml + - toc: elasticsearch-ruby://release-notes + path_prefix: release-notes/elasticsearch/clients/ruby + # Elasticsearch Hadoop + # https://github.com/elastic/elasticsearch-hadoop/blob/main/docs/release-notes/toc.yml + - toc: elasticsearch-hadoop://release-notes + path_prefix: release-notes/elasticsearch-hadoop + + # Kibana + # https://github.com/elastic/kibana/blob/main/docs/release-notes/toc.yml + - toc: kibana://release-notes + path_prefix: release-notes/kibana + + # Elastic Agent + - toc: elastic-agent://release-notes + path_prefix: release-notes/elastic-agent + + # Fleet Server + - toc: fleet-server://release-notes + path_prefix: release-notes/fleet-server + + # Logstash + # https://github.com/elastic/logstash/blob/main/docs/release-notes/toc.yml + - toc: logstash://release-notes + path_prefix: release-notes/logstash + + # Beats + # https://github.com/elastic/beats/blob/main/docs/release-notes/toc.yml + - toc: beats://release-notes + path_prefix: release-notes/beats + + # Serverless + # Comes from docs-content + # https://github.com/elastic/docs-content/blob/main/release-notes/elastic-cloud-serverless/toc.yml + - toc: docs-content://release-notes/elastic-cloud-serverless + path_prefix: release-notes/cloud-serverless + + # Cloud Hosted + # https://github.com/elastic/cloud/blob/master/docs/release-notes/cloud-hosted/toc.yml + # We probably need to add `max_toc_depth: 2` to + # https://github.com/elastic/cloud/blob/master/docs/docset.yml + - toc: cloud://release-notes/cloud-hosted + path_prefix: release-notes/cloud-hosted + + # Cloud Enterprise + # https://github.com/elastic/cloud/blob/master/docs/release-notes/cloud-enterprise/toc.yml + - toc: cloud://release-notes/cloud-enterprise + path_prefix: release-notes/cloud-enterprise + + # K8s + # https://github.com/elastic/cloud-on-k8s/blob/main/docs/release-notes/toc.yml + - toc: cloud-on-k8s://release-notes + path_prefix: release-notes/cloud-on-k8s + + # Observability + # https://github.com/elastic/docs-content/blob/main/release-notes/elastic-observability/toc.yml + # Comes from docs-content + - toc: docs-content://release-notes/elastic-observability + path_prefix: release-notes/observability + children: + # EDOT Android + # https://github.com/elastic/apm-agent-android/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-android://release-notes + path_prefix: release-notes/edot/sdks/android + # Elastic Cloud Forwarder for AWS + # https://github.com/elastic/edot-cloud-forwarder-aws/blob/main/docs/release-notes/toc.yml + - toc: edot-cloud-forwarder-aws://release-notes + path_prefix: release-notes/edot/cloud-forwarder/aws + # EDOT iOS / Swift + # https://github.com/elastic/apm-agent-ios/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-ios://release-notes + path_prefix: release-notes/edot/sdks/ios + # EDOT Java + # https://github.com/elastic/elastic-otel-java/blob/main/docs/release-notes/toc.yml + - toc: elastic-otel-java://release-notes + path_prefix: release-notes/edot/sdks/java + # EDOT .NET + # https://github.com/elastic/elastic-otel-dotnet/blob/main/docs/release-notes/toc.yml + - toc: elastic-otel-dotnet://release-notes + path_prefix: release-notes/edot/sdks/dotnet + # EDOT Node + # https://github.com/elastic/elastic-otel-node/blob/main/docs/release-notes/toc.yml + - toc: elastic-otel-node://release-notes + path_prefix: release-notes/edot/sdks/node + # EDOT Python + # https://github.com/elastic/elastic-otel-python/blob/main/docs/release-notes/toc.yml + - toc: elastic-otel-python://release-notes + path_prefix: release-notes/edot/sdks/python + # EDOT PHP + # https://github.com/elastic/elastic-otel-php/blob/main/docs/release-notes/toc.yml + - toc: elastic-otel-php://release-notes + path_prefix: release-notes/edot/sdks/php + # EDOT Browser (RUM) + # https://github.com/elastic/elastic-otel-rum-js/blob/main/docs/release-notes/toc.yml + - toc: elastic-otel-rum-js://release-notes + path_prefix: release-notes/edot/sdks/rum + # APM + # https://github.com/elastic/docs-content/blob/main/release-notes/elastic-apm/toc.yml + - toc: apm-server://release-notes + path_prefix: release-notes/apm + # APM .NET agent + # https://github.com/elastic/apm-agent-dotnet/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-dotnet://release-notes + path_prefix: release-notes/apm/agents/dotnet + # APM Go agent + # https://github.com/elastic/apm-agent-go/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-go://release-notes + path_prefix: release-notes/apm/agents/go + # APM Java agent + # https://github.com/elastic/apm-agent-java/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-java://release-notes + path_prefix: release-notes/apm/agents/java + # APM Node.js agent + # https://github.com/elastic/apm-agent-nodejs/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-nodejs://release-notes + path_prefix: release-notes/apm/agents/nodejs + # APM PHP agent + # https://github.com/elastic/apm-agent-php/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-php://release-notes + path_prefix: release-notes/apm/agents/php + # APM Python agent + # https://github.com/elastic/apm-agent-python/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-python://release-notes + path_prefix: release-notes/apm/agents/python + # APM Ruby agent + # https://github.com/elastic/apm-agent-ruby/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-ruby://release-notes + path_prefix: release-notes/apm/agents/ruby + # APM RUM JavaScript Agent + # https://github.com/elastic/apm-agent-rum-js/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-rum-js://release-notes + path_prefix: release-notes/apm/agents/rum-js + # APM AWS Lambda Extension + # https://github.com/elastic/apm-aws-lambda/blob/main/docs/release-notes/toc.yml + - toc: apm-aws-lambda://release-notes + path_prefix: release-notes/apm/aws-lambda + + # Security + # https://github.com/elastic/docs-content/blob/main/release-notes/elastic-security/toc.yml + - toc: docs-content://release-notes/elastic-security + path_prefix: release-notes/security + + # ECS + # https://github.com/elastic/ecs/blob/main/docs/release-notes/toc.yml + - toc: ecs://release-notes + path_prefix: release-notes/ecs + + # ECCTL + # https://github.com/elastic/ecctl/blob/master/docs/release-notes/toc.yml + - toc: ecctl://release-notes + path_prefix: release-notes/ecctl ############# # REFERENCE # ############# - - toc: reference - path_prefix: reference + - section: Reference children: - - # Elasticsearch - # https://github.com/elastic/elasticsearch/blob/main/docs/reference/elasticsearch/toc.yml - - toc: elasticsearch://reference/elasticsearch - path_prefix: reference/elasticsearch - island: true - # Children include: Configuration, JVM settings, Roles, - # Elasticsearch privileges, Index settings, Index lifecycle actions, - # REST APIs, Mapping, Elasticsearch audit events, Command line tools + - toc: reference + path_prefix: reference children: - # Query languages - # https://github.com/elastic/elasticsearch/blob/main/docs/reference/query-languages/toc.yml - - toc: elasticsearch://reference/query-languages - path_prefix: reference/query-languages - island: true - - # Text analysis - # https://github.com/elastic/elasticsearch/blob/main/docs/reference/text-analysis/toc.yml - - toc: elasticsearch://reference/text-analysis - path_prefix: reference/text-analysis - - # https://github.com/elastic/elasticsearch/blob/main/docs/reference/aggregations/toc.yml - - toc: elasticsearch://reference/aggregations - path_prefix: reference/aggregations - - # Processor reference - # https://github.com/elastic/elasticsearch/blob/main/docs/reference/ingest-processor/toc.yml - - toc: elasticsearch://reference/ingest-processor - path_prefix: reference/ingest-processor - - # Curator - # https://github.com/elastic/curator/blob/master/docs/reference/toc.yml - - toc: curator://reference - path_prefix: reference/elasticsearch/curator - island: true - # Children include the entire AsciiDoc book - - # Clients - # https://github.com/elastic/docs-content/blob/main/reference/elasticsearch-clients/toc.yml - - toc: docs-content://reference/elasticsearch-clients - path_prefix: reference/elasticsearch-clients - island: true + # Elasticsearch + # https://github.com/elastic/elasticsearch/blob/main/docs/reference/elasticsearch/toc.yml + - toc: elasticsearch://reference/elasticsearch + path_prefix: reference/elasticsearch + # Children include: Configuration, JVM settings, Roles, + # Elasticsearch privileges, Index settings, Index lifecycle actions, + # REST APIs, Mapping, Elasticsearch audit events, Command line tools children: - # Eland - # https://github.com/elastic/eland/blob/main/docs/reference/toc.yml - - toc: eland://reference - path_prefix: reference/elasticsearch/clients/eland - island: true + # Query languages + # https://github.com/elastic/elasticsearch/blob/main/docs/reference/query-languages/toc.yml + - toc: elasticsearch://reference/query-languages + path_prefix: reference/query-languages + + # Text analysis + # https://github.com/elastic/elasticsearch/blob/main/docs/reference/text-analysis/toc.yml + - toc: elasticsearch://reference/text-analysis + path_prefix: reference/text-analysis + + # https://github.com/elastic/elasticsearch/blob/main/docs/reference/aggregations/toc.yml + - toc: elasticsearch://reference/aggregations + path_prefix: reference/aggregations + + # Processor reference + # https://github.com/elastic/elasticsearch/blob/main/docs/reference/ingest-processor/toc.yml + - toc: elasticsearch://reference/ingest-processor + path_prefix: reference/ingest-processor + + # Curator + # https://github.com/elastic/curator/blob/master/docs/reference/toc.yml + - toc: curator://reference + path_prefix: reference/elasticsearch/curator # Children include the entire AsciiDoc book - # Go - # https://github.com/elastic/go-elasticsearch/blob/main/docs/reference/toc.yml - - toc: go-elasticsearch://reference - path_prefix: reference/elasticsearch/clients/go - island: true - # Children include the entire AsciiDoc book - - # Java - # https://github.com/elastic/elasticsearch-java/blob/main/docs/reference/toc.yml - - toc: elasticsearch-java://reference - path_prefix: reference/elasticsearch/clients/java - island: true - # Children include the entire AsciiDoc book - - # JavaScript - # https://github.com/elastic/elasticsearch-js/blob/main/docs/reference/toc.yml - - toc: elasticsearch-js://reference - path_prefix: reference/elasticsearch/clients/javascript - island: true - # Children include the entire AsciiDoc book - - # JavaScript/TypeScript DSL - # https://github.com/elastic/elasticsearch-dsl-js/blob/main/docs/reference/toc.yml - - toc: elasticsearch-dsl-js://reference - path_prefix: reference/elasticsearch/clients/javascript-dsl - island: true - - # .NET - # https://github.com/elastic/elasticsearch-net/blob/main/docs/reference/toc.yml - - toc: elasticsearch-net://reference - path_prefix: reference/elasticsearch/clients/dotnet - island: true - # Children include the entire AsciiDoc book - - # PHP - # https://github.com/elastic/elasticsearch-php/blob/main/docs/reference/toc.yml - - toc: elasticsearch-php://reference - path_prefix: reference/elasticsearch/clients/php - island: true - # Children include the entire AsciiDoc book - - # Python - # https://github.com/elastic/elasticsearch-py/blob/main/docs/reference/toc.yml - - toc: elasticsearch-py://reference - path_prefix: reference/elasticsearch/clients/python - island: true - # Children include the entire AsciiDoc book - - # Ruby - # https://github.com/elastic/elasticsearch-ruby/blob/main/docs/reference/toc.yml - - toc: elasticsearch-ruby://reference - path_prefix: reference/elasticsearch/clients/ruby - island: true - # Children include the entire AsciiDoc book - - # Rust - # https://github.com/elastic/elasticsearch-rs/blob/main/docs/reference/toc.yml - - toc: elasticsearch-rs://reference - path_prefix: reference/elasticsearch/clients/rust - island: true - # Children include the entire AsciiDoc book - - # Community-contributed clients - # https://github.com/elastic/elasticsearch/blob/main/docs/reference/community-contributed/toc.yml - - toc: elasticsearch://reference/community-contributed - path_prefix: reference/elasticsearch/clients/community - - # Elasticsearch plugins - # https://github.com/elastic/elasticsearch/blob/main/docs/reference/elasticsearch-plugins/toc.yml - - toc: elasticsearch://reference/elasticsearch-plugins - path_prefix: reference/elasticsearch/plugins - island: true - - # Painless scripting language - # https://github.com/elastic/elasticsearch/blob/main/docs/reference/scripting-languages/toc.yml - - toc: elasticsearch://reference/scripting-languages - path_prefix: reference/scripting-languages - island: true - - # Kibana - # https://github.com/elastic/kibana/blob/main/docs/reference/toc.yml - - toc: kibana://reference - path_prefix: reference/kibana - island: true - # Children include the entire AsciiDoc book - # (minus pages moved to docs-content) - - # Cloud - # https://github.com/elastic/cloud/blob/master/docs/reference/toc.yml - - toc: cloud://reference - path_prefix: reference/cloud - island: true - # Children include Elastic Cloud Enterprise, Elastic Cloud Hosted - children: - # Elastic Cloud on Kubernetes - # https://github.com/elastic/cloud-on-k8s/blob/main/docs/reference/toc.yml - - toc: cloud-on-k8s://reference - path_prefix: reference/cloud-on-k8s - island: true - # Children include the entire AsciiDoc book - # (minus pages moved to docs-content) - - # Elastic cloud control (ECCTL) - # https://github.com/elastic/ecctl/blob/master/docs/reference/toc.yml - - toc: ecctl://reference - path_prefix: reference/ecctl - island: true - # Children include the entire AsciiDoc book - # (minus pages moved to docs-content) - - # Security - # https://github.com/elastic/docs-content/blob/main/reference/security/toc.yml - - toc: docs-content://reference/security - path_prefix: reference/security - island: true - # Children include: Endpoint command reference, Elastic Defend, - # Fields and object schemas - children: - - toc: detection-rules:// - path_prefix: reference/security/prebuilt-rules - island: true - - # Observability - # https://github.com/elastic/docs-content/blob/main/reference/observability/toc.yml - - toc: docs-content://reference/observability - path_prefix: reference/observability - island: true - # Children include: Fields and object schemas, Elastic Entity Model, - # Infrastructure app fields - - # Ingestion tools - # https://github.com/elastic/docs-content/blob/main/reference/ingestion-tools/toc.yml - - toc: docs-content://reference/ingestion-tools - path_prefix: reference/ingestion-tools - island: true - children: - # APM - # https://github.com/elastic/docs-content/blob/main/reference/apm/toc.yml - - toc: docs-content://reference/apm - path_prefix: reference/apm - island: true - # Children include: APM settings, APM settings for Elastic Cloud, - # APM settings for Elastic Cloud Enterprise - children: - # APM Attacher for Kubernetes - # https://github.com/elastic/apm-k8s-attacher/blob/main/docs/reference/toc.yml - - toc: apm-k8s-attacher://reference - path_prefix: reference/apm/k8s-attacher - island: true - # Children include the entire AsciiDoc book - - # APM Architecture for AWS Lambda - # https://github.com/elastic/apm-aws-lambda/blob/main/docs/reference/toc.yml - - toc: apm-aws-lambda://reference - path_prefix: reference/apm/aws-lambda - island: true - # Children include the entire AsciiDoc book - - # APM agents - # https://github.com/elastic/docs-content/blob/main/reference/apm-agents/toc.yml - - toc: docs-content://reference/apm-agents - path_prefix: reference/apm-agents - island: true + # Clients + # https://github.com/elastic/docs-content/blob/main/reference/elasticsearch-clients/toc.yml + - toc: docs-content://reference/elasticsearch-clients + path_prefix: reference/elasticsearch-clients children: - # APM .NET agent - # https://github.com/elastic/apm-agent-dotnet/blob/main/docs/reference/toc.yml - - toc: apm-agent-dotnet://reference - path_prefix: reference/apm/agents/dotnet - island: true + # Eland + # https://github.com/elastic/eland/blob/main/docs/reference/toc.yml + - toc: eland://reference + path_prefix: reference/elasticsearch/clients/eland # Children include the entire AsciiDoc book - # APM Go agent - # https://github.com/elastic/apm-agent-go/blob/main/docs/reference/toc.yml - - toc: apm-agent-go://reference - path_prefix: reference/apm/agents/go - island: true + # Go + # https://github.com/elastic/go-elasticsearch/blob/main/docs/reference/toc.yml + - toc: go-elasticsearch://reference + path_prefix: reference/elasticsearch/clients/go # Children include the entire AsciiDoc book - # APM Java agent - # https://github.com/elastic/apm-agent-java/blob/main/docs/reference/toc.yml - - toc: apm-agent-java://reference - path_prefix: reference/apm/agents/java - island: true + # Java + # https://github.com/elastic/elasticsearch-java/blob/main/docs/reference/toc.yml + - toc: elasticsearch-java://reference + path_prefix: reference/elasticsearch/clients/java # Children include the entire AsciiDoc book - # APM Node.js agent - # https://github.com/elastic/apm-agent-nodejs/blob/main/docs/reference/toc.yml - - toc: apm-agent-nodejs://reference - path_prefix: reference/apm/agents/nodejs - island: true + # JavaScript + # https://github.com/elastic/elasticsearch-js/blob/main/docs/reference/toc.yml + - toc: elasticsearch-js://reference + path_prefix: reference/elasticsearch/clients/javascript # Children include the entire AsciiDoc book - # APM PHP agent - # https://github.com/elastic/apm-agent-php/blob/main/docs/reference/toc.yml - - toc: apm-agent-php://reference - path_prefix: reference/apm/agents/php - island: true - # Children include the entire AsciiDoc book + # JavaScript/TypeScript DSL + # https://github.com/elastic/elasticsearch-dsl-js/blob/main/docs/reference/toc.yml + - toc: elasticsearch-dsl-js://reference + path_prefix: reference/elasticsearch/clients/javascript-dsl - # APM Python agent - # https://github.com/elastic/apm-agent-python/blob/main/docs/reference/toc.yml - - toc: apm-agent-python://reference - path_prefix: reference/apm/agents/python - island: true + # .NET + # https://github.com/elastic/elasticsearch-net/blob/main/docs/reference/toc.yml + - toc: elasticsearch-net://reference + path_prefix: reference/elasticsearch/clients/dotnet # Children include the entire AsciiDoc book - # APM Ruby agent - # https://github.com/elastic/apm-agent-ruby/blob/main/docs/reference/toc.yml - - toc: apm-agent-ruby://reference - path_prefix: reference/apm/agents/ruby - island: true + # PHP + # https://github.com/elastic/elasticsearch-php/blob/main/docs/reference/toc.yml + - toc: elasticsearch-php://reference + path_prefix: reference/elasticsearch/clients/php # Children include the entire AsciiDoc book - # APM RUM JavaScript agent - # https://github.com/elastic/apm-agent-rum-js/blob/main/docs/reference/toc.yml - - toc: apm-agent-rum-js://reference - path_prefix: reference/apm/agents/rum-js - island: true + # Python + # https://github.com/elastic/elasticsearch-py/blob/main/docs/reference/toc.yml + - toc: elasticsearch-py://reference + path_prefix: reference/elasticsearch/clients/python # Children include the entire AsciiDoc book - # Beats - # https://github.com/elastic/beats/blob/main/docs/reference/toc.yml - - toc: beats://reference - path_prefix: reference/beats - island: true - # Children include all entire AsciiDoc books: Beats, Auditbeat, - # Filebeat, Heartbeat, Metricbeat, Packetbeat, Winlogbeat, - # Elastic logging plugin for Docker - - # Search connectors - # https://github.com/elastic/elasticsearch/blob/main/docs/reference/search-connectors/toc.yml - - toc: elasticsearch://reference/search-connectors - path_prefix: reference/search-connectors - island: true - - # Elastic Distributions of OpenTelemetry (EDOT) - # https://github.com/elastic/opentelemetry/blob/main/docs/reference/toc.yml - - toc: opentelemetry://reference - path_prefix: reference/opentelemetry - island: true - children: - - toc: opentelemetry://reference/edot-cloud-forwarder - path_prefix: reference/opentelemetry/edot-cloud-forwarder - island: true - children: - - toc: edot-cloud-forwarder-aws://reference/edot-cf-aws - path_prefix: reference/opentelemetry/edot-cloud-forwarder/aws - - toc: edot-cloud-forwarder-azure://reference/edot-cf-azure - path_prefix: reference/opentelemetry/edot-cloud-forwarder/azure - - toc: opentelemetry://reference/edot-cloud-forwarder/gcp - path_prefix: reference/opentelemetry/edot-cloud-forwarder/gcp - - toc: elastic-agent://reference/edot-collector - path_prefix: reference/edot-collector - island: true - - toc: apm-agent-android://reference/edot-android - path_prefix: reference/opentelemetry/edot-sdks/android - island: true - - toc: elastic-otel-dotnet://reference/edot-dotnet - path_prefix: reference/opentelemetry/edot-sdks/dotnet - island: true - - toc: apm-agent-ios://reference/edot-ios - path_prefix: reference/opentelemetry/edot-sdks/ios - island: true - - toc: elastic-otel-java://reference/edot-java - path_prefix: reference/opentelemetry/edot-sdks/java - island: true - - toc: elastic-otel-node://reference/edot-node - path_prefix: reference/opentelemetry/edot-sdks/node - island: true - - toc: elastic-otel-php://reference/edot-php - path_prefix: reference/opentelemetry/edot-sdks/php - island: true - - toc: elastic-otel-python://reference/edot-python - path_prefix: reference/opentelemetry/edot-sdks/python - island: true - # EDOT Browser (RUM) - # https://github.com/elastic/elastic-otel-rum-js/blob/main/docs/reference/edot-browser/toc.yml - - toc: elastic-otel-rum-js://reference/edot-browser - path_prefix: reference/opentelemetry/edot-sdks/browser - island: true - - # Managed inputs - # https://github.com/elastic/opentelemetry/blob/main/docs/reference/managed-inputs/toc.yml - - toc: opentelemetry://reference/managed-inputs - path_prefix: reference/opentelemetry/managed-inputs - - # Elastic Integrations - # https://github.com/elastic/integration-docs/blob/main/docs/reference/toc.yml - - toc: integration-docs://reference - path_prefix: reference/integrations - island: true - # Children include the entire AsciiDoc book - - # Elastic Serverless Forwarder for AWS - # https://github.com/elastic/elastic-serverless-forwarder/blob/main/docs/reference/toc.yml - - toc: elastic-serverless-forwarder://reference - path_prefix: reference/aws-forwarder - island: true - # Children include the entire AsciiDoc book - - # Elasticsearch Hadoop - # https://github.com/elastic/elasticsearch-hadoop/blob/main/docs/reference/toc.yml - - toc: elasticsearch-hadoop://reference - path_prefix: reference/elasticsearch-hadoop - island: true - # Children include the entire AsciiDoc book - - # Fleet and Elastic Agent - # https://github.com/elastic/docs-content/blob/main/reference/fleet/toc.yml - - toc: docs-content://reference/fleet - path_prefix: reference/fleet - island: true - # Children include the entire AsciiDoc book - - # Logstash - # https://github.com/elastic/logstash/blob/main/docs/reference/toc.yml - - toc: logstash://reference - path_prefix: reference/logstash - island: true - # Children include the entire AsciiDoc book + # Ruby + # https://github.com/elastic/elasticsearch-ruby/blob/main/docs/reference/toc.yml + - toc: elasticsearch-ruby://reference + path_prefix: reference/elasticsearch/clients/ruby + # Children include the entire AsciiDoc book - # # Logstash plugins (LSR) - - toc: logstash-docs-md://lsr - path_prefix: reference/logstash/plugins - island: true - # # Children include the entire AsciiDoc book + # Rust + # https://github.com/elastic/elasticsearch-rs/blob/main/docs/reference/toc.yml + - toc: elasticsearch-rs://reference + path_prefix: reference/elasticsearch/clients/rust + # Children include the entire AsciiDoc book - # # Logstash versioned plugins (VPR) - - toc: logstash-docs-md://vpr - path_prefix: reference/logstash/versioned-plugins - island: true + # Community-contributed clients + # https://github.com/elastic/elasticsearch/blob/main/docs/reference/community-contributed/toc.yml + - toc: elasticsearch://reference/community-contributed + path_prefix: reference/elasticsearch/clients/community + + # Elasticsearch plugins + # https://github.com/elastic/elasticsearch/blob/main/docs/reference/elasticsearch-plugins/toc.yml + - toc: elasticsearch://reference/elasticsearch-plugins + path_prefix: reference/elasticsearch/plugins + + # Painless scripting language + # https://github.com/elastic/elasticsearch/blob/main/docs/reference/scripting-languages/toc.yml + - toc: elasticsearch://reference/scripting-languages + path_prefix: reference/scripting-languages + + # Kibana + # https://github.com/elastic/kibana/blob/main/docs/reference/toc.yml + - toc: kibana://reference + path_prefix: reference/kibana # Children include the entire AsciiDoc book + # (minus pages moved to docs-content) - # ECS - # https://github.com/elastic/ecs/blob/main/docs/reference/toc.yml - - toc: ecs://reference - path_prefix: reference/ecs - island: true - # Children include the entire AsciiDoc book - children: - # ECS logging libraries - # https://github.com/elastic/ecs-logging/blob/main/docs/reference/toc.yml - - toc: ecs-logging://reference - path_prefix: reference/ecs/logging - island: true + # Cloud + # https://github.com/elastic/cloud/blob/master/docs/reference/toc.yml + - toc: cloud://reference + path_prefix: reference/cloud + # Children include Elastic Cloud Enterprise, Elastic Cloud Hosted children: - # ECS Logging .NET - # https://github.com/elastic/ecs-dotnet/blob/main/docs/reference/toc.yml - - toc: ecs-dotnet://reference - path_prefix: reference/ecs/logging/dotnet - island: true + # Elastic Cloud on Kubernetes + # https://github.com/elastic/cloud-on-k8s/blob/main/docs/reference/toc.yml + - toc: cloud-on-k8s://reference + path_prefix: reference/cloud-on-k8s # Children include the entire AsciiDoc book + # (minus pages moved to docs-content) - # ECS Logging Go (Logrus) - # https://github.com/elastic/ecs-logging-go-logrus/blob/main/docs/reference/toc.yml - - toc: ecs-logging-go-logrus://reference - path_prefix: reference/ecs/logging/go-logrus - island: true + # Elastic cloud control (ECCTL) + # https://github.com/elastic/ecctl/blob/master/docs/reference/toc.yml + - toc: ecctl://reference + path_prefix: reference/ecctl # Children include the entire AsciiDoc book + # (minus pages moved to docs-content) + + # Security + # https://github.com/elastic/docs-content/blob/main/reference/security/toc.yml + - toc: docs-content://reference/security + path_prefix: reference/security + # Children include: Endpoint command reference, Elastic Defend, + # Fields and object schemas + children: + - toc: detection-rules:// + path_prefix: reference/security/prebuilt-rules + + # Observability + # https://github.com/elastic/docs-content/blob/main/reference/observability/toc.yml + - toc: docs-content://reference/observability + path_prefix: reference/observability + # Children include: Fields and object schemas, Elastic Entity Model, + # Infrastructure app fields + + # Ingestion tools + # https://github.com/elastic/docs-content/blob/main/reference/ingestion-tools/toc.yml + - toc: docs-content://reference/ingestion-tools + path_prefix: reference/ingestion-tools + children: + # APM + # https://github.com/elastic/docs-content/blob/main/reference/apm/toc.yml + - toc: docs-content://reference/apm + path_prefix: reference/apm + # Children include: APM settings, APM settings for Elastic Cloud, + # APM settings for Elastic Cloud Enterprise + children: + # APM Attacher for Kubernetes + # https://github.com/elastic/apm-k8s-attacher/blob/main/docs/reference/toc.yml + - toc: apm-k8s-attacher://reference + path_prefix: reference/apm/k8s-attacher + # Children include the entire AsciiDoc book - # ECS Logging Go (Zap) - # https://github.com/elastic/ecs-logging-go-zap/blob/main/docs/reference/toc.yml - - toc: ecs-logging-go-zap://reference - path_prefix: reference/ecs/logging/go-zap - island: true - # Children include the entire AsciiDoc book + # APM Architecture for AWS Lambda + # https://github.com/elastic/apm-aws-lambda/blob/main/docs/reference/toc.yml + - toc: apm-aws-lambda://reference + path_prefix: reference/apm/aws-lambda + # Children include the entire AsciiDoc book - # ECS Logging Go (Zerolog) - # https://github.com/elastic/ecs-logging-go-zerolog/blob/main/docs/reference/toc.yml - - toc: ecs-logging-go-zerolog://reference - path_prefix: reference/ecs/logging/go-zerolog - island: true + # APM agents + # https://github.com/elastic/docs-content/blob/main/reference/apm-agents/toc.yml + - toc: docs-content://reference/apm-agents + path_prefix: reference/apm-agents + children: + + # APM .NET agent + # https://github.com/elastic/apm-agent-dotnet/blob/main/docs/reference/toc.yml + - toc: apm-agent-dotnet://reference + path_prefix: reference/apm/agents/dotnet + # Children include the entire AsciiDoc book + + # APM Go agent + # https://github.com/elastic/apm-agent-go/blob/main/docs/reference/toc.yml + - toc: apm-agent-go://reference + path_prefix: reference/apm/agents/go + # Children include the entire AsciiDoc book + + # APM Java agent + # https://github.com/elastic/apm-agent-java/blob/main/docs/reference/toc.yml + - toc: apm-agent-java://reference + path_prefix: reference/apm/agents/java + # Children include the entire AsciiDoc book + + # APM Node.js agent + # https://github.com/elastic/apm-agent-nodejs/blob/main/docs/reference/toc.yml + - toc: apm-agent-nodejs://reference + path_prefix: reference/apm/agents/nodejs + # Children include the entire AsciiDoc book + + # APM PHP agent + # https://github.com/elastic/apm-agent-php/blob/main/docs/reference/toc.yml + - toc: apm-agent-php://reference + path_prefix: reference/apm/agents/php + # Children include the entire AsciiDoc book + + # APM Python agent + # https://github.com/elastic/apm-agent-python/blob/main/docs/release-notes/toc.yml + - toc: apm-agent-python://reference + path_prefix: reference/apm/agents/python + # Children include the entire AsciiDoc book + + # APM Ruby agent + # https://github.com/elastic/apm-agent-ruby/blob/main/docs/reference/toc.yml + - toc: apm-agent-ruby://reference + path_prefix: reference/apm/agents/ruby + # Children include the entire AsciiDoc book + + # APM RUM JavaScript agent + # https://github.com/elastic/apm-agent-rum-js/blob/main/docs/reference/toc.yml + - toc: apm-agent-rum-js://reference + path_prefix: reference/apm/agents/rum-js + # Children include the entire AsciiDoc book + + # Beats + # https://github.com/elastic/beats/blob/main/docs/reference/toc.yml + - toc: beats://reference + path_prefix: reference/beats + # Children include all entire AsciiDoc books: Beats, Auditbeat, + # Filebeat, Heartbeat, Metricbeat, Packetbeat, Winlogbeat, + # Elastic logging plugin for Docker + + # Search connectors + # https://github.com/elastic/elasticsearch/blob/main/docs/reference/search-connectors/toc.yml + - toc: elasticsearch://reference/search-connectors + path_prefix: reference/search-connectors + + # Elastic Distributions of OpenTelemetry (EDOT) + # https://github.com/elastic/opentelemetry/blob/main/docs/reference/toc.yml + - toc: opentelemetry://reference + path_prefix: reference/opentelemetry + children: + - toc: opentelemetry://reference/edot-cloud-forwarder + path_prefix: reference/opentelemetry/edot-cloud-forwarder + children: + - toc: edot-cloud-forwarder-aws://reference/edot-cf-aws + path_prefix: reference/opentelemetry/edot-cloud-forwarder/aws + - toc: edot-cloud-forwarder-azure://reference/edot-cf-azure + path_prefix: reference/opentelemetry/edot-cloud-forwarder/azure + - toc: opentelemetry://reference/edot-cloud-forwarder/gcp + path_prefix: reference/opentelemetry/edot-cloud-forwarder/gcp + - toc: elastic-agent://reference/edot-collector + path_prefix: reference/edot-collector + - toc: apm-agent-android://reference/edot-android + path_prefix: reference/opentelemetry/edot-sdks/android + - toc: elastic-otel-dotnet://reference/edot-dotnet + path_prefix: reference/opentelemetry/edot-sdks/dotnet + - toc: apm-agent-ios://reference/edot-ios + path_prefix: reference/opentelemetry/edot-sdks/ios + - toc: elastic-otel-java://reference/edot-java + path_prefix: reference/opentelemetry/edot-sdks/java + - toc: elastic-otel-node://reference/edot-node + path_prefix: reference/opentelemetry/edot-sdks/node + - toc: elastic-otel-php://reference/edot-php + path_prefix: reference/opentelemetry/edot-sdks/php + - toc: elastic-otel-python://reference/edot-python + path_prefix: reference/opentelemetry/edot-sdks/python + # EDOT Browser (RUM) + # https://github.com/elastic/elastic-otel-rum-js/blob/main/docs/reference/edot-browser/toc.yml + - toc: elastic-otel-rum-js://reference/edot-browser + path_prefix: reference/opentelemetry/edot-sdks/browser + + # Managed inputs + # https://github.com/elastic/opentelemetry/blob/main/docs/reference/managed-inputs/toc.yml + - toc: opentelemetry://reference/managed-inputs + path_prefix: reference/opentelemetry/managed-inputs + + # Elastic Integrations + # https://github.com/elastic/integration-docs/blob/main/docs/reference/toc.yml + - toc: integration-docs://reference + path_prefix: reference/integrations # Children include the entire AsciiDoc book - # ECS Logging Java - # https://github.com/elastic/ecs-logging-java/blob/main/docs/reference/toc.yml - - toc: ecs-logging-java://reference - path_prefix: reference/ecs/logging/java - island: true + # Elastic Serverless Forwarder for AWS + # https://github.com/elastic/elastic-serverless-forwarder/blob/main/docs/reference/toc.yml + - toc: elastic-serverless-forwarder://reference + path_prefix: reference/aws-forwarder # Children include the entire AsciiDoc book - # ECS Logging Node.js - # https://github.com/elastic/ecs-logging-nodejs/blob/main/docs/reference/toc.yml - - toc: ecs-logging-nodejs://reference - path_prefix: reference/ecs/logging/nodejs - island: true + # Elasticsearch Hadoop + # https://github.com/elastic/elasticsearch-hadoop/blob/main/docs/reference/toc.yml + - toc: elasticsearch-hadoop://reference + path_prefix: reference/elasticsearch-hadoop # Children include the entire AsciiDoc book - # ECS Logging PHP - # https://github.com/elastic/ecs-logging-php/blob/main/docs/reference/toc.yml - - toc: ecs-logging-php://reference - path_prefix: reference/ecs/logging/php - island: true + # Fleet and Elastic Agent + # https://github.com/elastic/docs-content/blob/main/reference/fleet/toc.yml + - toc: docs-content://reference/fleet + path_prefix: reference/fleet # Children include the entire AsciiDoc book - # ECS Logging Python - # https://github.com/elastic/ecs-logging-python/blob/main/docs/reference/toc.yml - - toc: ecs-logging-python://reference - path_prefix: reference/ecs/logging/python - island: true + # Logstash + # https://github.com/elastic/logstash/blob/main/docs/reference/toc.yml + - toc: logstash://reference + path_prefix: reference/logstash # Children include the entire AsciiDoc book - # ECS Logging Ruby - # https://github.com/elastic/ecs-logging-ruby/blob/main/docs/reference/toc.yml - - toc: ecs-logging-ruby://reference - path_prefix: reference/ecs/logging/ruby - island: true + # # Logstash plugins (LSR) + - toc: logstash-docs-md://lsr + path_prefix: reference/logstash/plugins + # # Children include the entire AsciiDoc book + + # # Logstash versioned plugins (VPR) + - toc: logstash-docs-md://vpr + path_prefix: reference/logstash/versioned-plugins # Children include the entire AsciiDoc book - # Machine learning (FKA Data analysis) - # https://github.com/elastic/docs-content/blob/main/reference/machine-learning/toc.yml - - toc: docs-content://reference/machine-learning - path_prefix: reference/machine-learning - island: true + # ECS + # https://github.com/elastic/ecs/blob/main/docs/reference/toc.yml + - toc: ecs://reference + path_prefix: reference/ecs + # Children include the entire AsciiDoc book + children: + # ECS logging libraries + # https://github.com/elastic/ecs-logging/blob/main/docs/reference/toc.yml + - toc: ecs-logging://reference + path_prefix: reference/ecs/logging + children: + # ECS Logging .NET + # https://github.com/elastic/ecs-dotnet/blob/main/docs/reference/toc.yml + - toc: ecs-dotnet://reference + path_prefix: reference/ecs/logging/dotnet + # Children include the entire AsciiDoc book - # Search UI - # https://github.com/elastic/search-ui/blob/main/docs/reference/toc.yml - - toc: search-ui://reference - path_prefix: reference/search-ui - island: true - # Children include the entire AsciiDoc book + # ECS Logging Go (Logrus) + # https://github.com/elastic/ecs-logging-go-logrus/blob/main/docs/reference/toc.yml + - toc: ecs-logging-go-logrus://reference + path_prefix: reference/ecs/logging/go-logrus + # Children include the entire AsciiDoc book - # Glossary - # https://github.com/elastic/docs-content/blob/main/reference/glossary/toc.yml - - toc: docs-content://reference/glossary - path_prefix: reference/glossary + # ECS Logging Go (Zap) + # https://github.com/elastic/ecs-logging-go-zap/blob/main/docs/reference/toc.yml + - toc: ecs-logging-go-zap://reference + path_prefix: reference/ecs/logging/go-zap + # Children include the entire AsciiDoc book + + # ECS Logging Go (Zerolog) + # https://github.com/elastic/ecs-logging-go-zerolog/blob/main/docs/reference/toc.yml + - toc: ecs-logging-go-zerolog://reference + path_prefix: reference/ecs/logging/go-zerolog + # Children include the entire AsciiDoc book + + # ECS Logging Java + # https://github.com/elastic/ecs-logging-java/blob/main/docs/reference/toc.yml + - toc: ecs-logging-java://reference + path_prefix: reference/ecs/logging/java + # Children include the entire AsciiDoc book + + # ECS Logging Node.js + # https://github.com/elastic/ecs-logging-nodejs/blob/main/docs/reference/toc.yml + - toc: ecs-logging-nodejs://reference + path_prefix: reference/ecs/logging/nodejs + # Children include the entire AsciiDoc book + + # ECS Logging PHP + # https://github.com/elastic/ecs-logging-php/blob/main/docs/reference/toc.yml + - toc: ecs-logging-php://reference + path_prefix: reference/ecs/logging/php + # Children include the entire AsciiDoc book + + # ECS Logging Python + # https://github.com/elastic/ecs-logging-python/blob/main/docs/reference/toc.yml + - toc: ecs-logging-python://reference + path_prefix: reference/ecs/logging/python + # Children include the entire AsciiDoc book + + # ECS Logging Ruby + # https://github.com/elastic/ecs-logging-ruby/blob/main/docs/reference/toc.yml + - toc: ecs-logging-ruby://reference + path_prefix: reference/ecs/logging/ruby + # Children include the entire AsciiDoc book + + # Machine learning (FKA Data analysis) + # https://github.com/elastic/docs-content/blob/main/reference/machine-learning/toc.yml + - toc: docs-content://reference/machine-learning + path_prefix: reference/machine-learning + + # Search UI + # https://github.com/elastic/search-ui/blob/main/docs/reference/toc.yml + - toc: search-ui://reference + path_prefix: reference/search-ui + # Children include the entire AsciiDoc book + + # Glossary + # https://github.com/elastic/docs-content/blob/main/reference/glossary/toc.yml + - toc: docs-content://reference/glossary + path_prefix: reference/glossary ########## # EXTEND # ########## - - toc: extend + - section: Extend children: - - toc: kibana://extend - path_prefix: extend/kibana - - toc: logstash://extend - path_prefix: extend/logstash - - toc: beats://extend - path_prefix: extend/beats - - toc: elasticsearch://extend - path_prefix: extend/elasticsearch - - toc: integrations://extend - path_prefix: extend/integrations - - ############# - # CONTRIBUTE 2 DOCS # - ############# - - toc: contribute-docs + - toc: extend + children: + - toc: kibana://extend + path_prefix: extend/kibana + - toc: logstash://extend + path_prefix: extend/logstash + - toc: beats://extend + path_prefix: extend/beats + - toc: elasticsearch://extend + path_prefix: extend/elasticsearch + - toc: integrations://extend + path_prefix: extend/integrations + - toc: contribute-docs diff --git a/src/Elastic.Documentation.Configuration/BuildContext.cs b/src/Elastic.Documentation.Configuration/BuildContext.cs index 99835cdd01..36514883e9 100644 --- a/src/Elastic.Documentation.Configuration/BuildContext.cs +++ b/src/Elastic.Documentation.Configuration/BuildContext.cs @@ -46,6 +46,12 @@ public record BuildContext : IDocumentationSetContext, IDocumentationConfigurati public ConfigurationFile Configuration { get; private set; } public DocumentationSetFile ConfigurationYaml { get; set; } + /// + /// The resolved site-wide top navigation. Only assembler builds set this; when null the layout + /// falls back to its built-in links. + /// + public TopNavRenderModel? TopNav { get; set; } + public VersionsConfiguration VersionsConfiguration { get; } public ConfigurationFileProvider ConfigurationFileProvider { get; } public DocumentationEndpoints Endpoints { get; } diff --git a/src/Elastic.Documentation.Configuration/Toc/SiteNavigationFile.cs b/src/Elastic.Documentation.Configuration/Toc/SiteNavigationFile.cs index 1378038c27..a217c3f5ea 100644 --- a/src/Elastic.Documentation.Configuration/Toc/SiteNavigationFile.cs +++ b/src/Elastic.Documentation.Configuration/Toc/SiteNavigationFile.cs @@ -18,6 +18,18 @@ public record NavigationTocMapping public required string SourcePathPrefix { get; init; } } +public interface ISiteNavigationEntry +{ + IReadOnlyCollection Children { get; } +} + +public record SiteSectionRef( + string Title, + string? Url, + bool External, + IReadOnlyCollection Children +) : ISiteNavigationEntry; + [YamlSerializable] public class SiteNavigationFile { @@ -53,16 +65,20 @@ public static bool ValidatePathPrefixes(IDiagnosticsCollector collector, SiteNav public static ImmutableHashSet GetAllDeclaredSources(SiteNavigationFile siteNavigation) { var set = new HashSet(); - - foreach (var tocRef in siteNavigation.TableOfContents) - CollectSource(tocRef, set); - + foreach (var entry in siteNavigation.TableOfContents) + { + if (entry is SiteTableOfContentsRef tocRef) + CollectSource(tocRef, set); + else + foreach (var child in entry.Children) + CollectSource(child, set); + } return set.ToImmutableHashSet(); } + private static void CollectSource(SiteTableOfContentsRef tocRef, HashSet set) { _ = set.Add(tocRef.Source); - // Recursively collect from children foreach (var child in tocRef.Children) CollectSource(child, set); } @@ -70,23 +86,25 @@ private static void CollectSource(SiteTableOfContentsRef tocRef, HashSet se private static ImmutableHashSet GetAllPathPrefixes(SiteNavigationFile siteNavigation) { var set = new HashSet(); - - foreach (var tocRef in siteNavigation.TableOfContents) - CollectPathPrefixes(tocRef, set); - + foreach (var entry in siteNavigation.TableOfContents) + { + if (entry is SiteTableOfContentsRef tocRef) + CollectPathPrefixes(tocRef, set); + else + foreach (var child in entry.Children) + CollectPathPrefixes(child, set); + } return set.ToImmutableHashSet(); } private static void CollectPathPrefixes(SiteTableOfContentsRef tocRef, HashSet set) { - // Add path prefix for this toc ref if (!string.IsNullOrEmpty(tocRef.PathPrefix)) { var pathUri = new Uri($"{tocRef.Source.Scheme}://{tocRef.PathPrefix.TrimEnd('/')}/"); _ = set.Add(pathUri); } - // Recursively collect from children foreach (var child in tocRef.Children) CollectPathPrefixes(child, set); } @@ -114,14 +132,14 @@ public class PhantomRegistration public string Source { get; set; } = null!; } -public class SiteTableOfContents : List; +public class SiteTableOfContents : List; /// /// When true, the resolved navigation node is marked as an island from the assembler side. /// OR-ed with any island: true the content set already declares — can only enable, never disable. /// public record SiteTableOfContentsRef(Uri Source, string PathPrefix, IReadOnlyCollection Children, bool Island = false) - : ITableOfContentsItem + : ISiteNavigationEntry, ITableOfContentsItem { // For site-level TOC refs, the Path is the path prefix (where it will be mounted in the site) public string PathRelativeToDocumentationSet => PathPrefix; @@ -148,14 +166,92 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria while (!parser.TryConsume(out _)) { - var item = rootDeserializer(typeof(SiteTableOfContentsRef)); - if (item is SiteTableOfContentsRef tocRef) - collection.Add(tocRef); + var entry = ParseTopLevelEntry(parser, rootDeserializer); + if (entry is not null) + collection.Add(entry); } return collection; } + private static ISiteNavigationEntry? ParseTopLevelEntry(IParser parser, ObjectDeserializer rootDeserializer) + { + if (!parser.TryConsume(out _)) + return null; + + var dictionary = new Dictionary(); + + while (!parser.TryConsume(out _)) + { + var key = parser.Consume(); + + object? value = null; + if (parser.Accept(out var scalarValue)) + { + value = scalarValue.Value; + _ = parser.MoveNext(); + } + else if (parser.Accept(out _)) + { + if (key.Value is "children") + { + var childrenList = new List(); + _ = parser.Consume(); + while (!parser.TryConsume(out _)) + { + var child = rootDeserializer(typeof(SiteTableOfContentsRef)); + if (child is SiteTableOfContentsRef childRef) + childrenList.Add(childRef); + } + value = childrenList; + } + else + parser.SkipThisAndNestedEvents(); + } + else if (parser.Accept(out _)) + parser.SkipThisAndNestedEvents(); + + dictionary[key.Value] = value; + } + + if (dictionary.TryGetValue("section", out var sectionTitleVal) && sectionTitleVal is string sectionTitle) + { + var url = dictionary.TryGetValue("url", out var urlVal) && urlVal is string u ? u : null; + var external = dictionary.TryGetValue("external", out var extVal) && extVal is string e + && bool.TryParse(e, out var eb) && eb; + IReadOnlyCollection children = dictionary.TryGetValue("children", out var childrenObj) && childrenObj is List refs + ? refs + : []; + return new SiteSectionRef(sectionTitle, url, external, children); + } + + if (dictionary.TryGetValue("toc", out var tocPath) && tocPath is string sourceString) + { + var uriString = sourceString.Contains("://") ? sourceString : $"docs-content://{sourceString}"; + + if (!Uri.TryCreate(uriString, UriKind.Absolute, out var source)) + throw new InvalidOperationException($"Invalid TOC source: '{sourceString}' could not be parsed as a URI"); + + var pathPrefix = dictionary.TryGetValue("path_prefix", out var pathValue) && pathValue is string path + ? path + : string.Empty; + + IReadOnlyCollection children = dictionary.TryGetValue("children", out var childrenObj2) && childrenObj2 is List tocRefs + ? tocRefs + : []; + + var island = dictionary.TryGetValue("island", out var islandObj) && islandObj is string islandStr + && bool.TryParse(islandStr, out var islandBool) && islandBool; + + return new SiteTableOfContentsRef(source, pathPrefix, children, island); + } + + var keys = string.Join(", ", dictionary.Keys.Select(k => $"'{k}'")); + throw new YamlException( + $"toc entry has no 'toc:' key and will be ignored. " + + $"Found keys: {keys}. Check for typos."); + } + public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) => serializer.Invoke(value, type); } @@ -175,7 +271,6 @@ public class SiteTableOfContentsRefYamlConverter : IYamlTypeConverter { var key = parser.Consume(); - // Parse the value based on what type it is object? value = null; if (parser.Accept(out var scalarValue)) { @@ -184,10 +279,8 @@ public class SiteTableOfContentsRefYamlConverter : IYamlTypeConverter } else if (parser.Accept(out _)) { - // This is a list - parse it manually for "children" if (key.Value == "children") { - // Parse the children list manually var childrenList = new List(); _ = parser.Consume(); while (!parser.TryConsume(out _)) @@ -199,26 +292,16 @@ public class SiteTableOfContentsRefYamlConverter : IYamlTypeConverter value = childrenList; } else - { - // For other lists, just skip them parser.SkipThisAndNestedEvents(); - } } else if (parser.Accept(out _)) - { - // This is a nested mapping - skip it parser.SkipThisAndNestedEvents(); - } dictionary[key.Value] = value; } - var children = GetChildren(dictionary); - - // Check for toc reference - required if (dictionary.TryGetValue("toc", out var tocPath) && tocPath is string sourceString) { - // Convert string to Uri - if no scheme, prepend "docs-content://" var uriString = sourceString.Contains("://") ? sourceString : $"docs-content://{sourceString}"; if (!Uri.TryCreate(uriString, UriKind.Absolute, out var source)) @@ -228,28 +311,22 @@ public class SiteTableOfContentsRefYamlConverter : IYamlTypeConverter ? path : string.Empty; + IReadOnlyCollection children = dictionary.TryGetValue("children", out var childrenObj) && childrenObj is List tocRefs + ? tocRefs + : []; + var island = dictionary.TryGetValue("island", out var islandObj) && islandObj is string islandStr && bool.TryParse(islandStr, out var islandBool) && islandBool; return new SiteTableOfContentsRef(source, pathPrefix, children, island); } - return null; - } - - private IReadOnlyCollection GetChildren(Dictionary dictionary) - { - if (!dictionary.TryGetValue("children", out var childrenObj)) - return []; - - // Children have already been deserialized as List - if (childrenObj is List tocRefs) - return tocRefs; - - return []; + var keys = string.Join(", ", dictionary.Keys.Select(k => $"'{k}'")); + throw new YamlException( + $"toc entry has no 'toc:' key and will be ignored. " + + $"Found keys: {keys}. Check for typos."); } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) => serializer.Invoke(value, type); } - diff --git a/src/Elastic.Documentation.Configuration/Toc/TopNavigation.cs b/src/Elastic.Documentation.Configuration/Toc/TopNavigation.cs new file mode 100644 index 0000000000..b4a61e4c10 --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Toc/TopNavigation.cs @@ -0,0 +1,55 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +namespace Elastic.Documentation.Configuration.Toc; + +/// +/// The resolved top navigation handed to the layout. Every URL here is final: cross links are +/// resolved and the environment path prefix is already applied, so templates render hrefs as is. +/// The active tab is determined by comparing each item's +/// against the current page's NavigationRoot.Id — no URL prefix matching. +/// +public record TopNavRenderModel(IReadOnlyList Items); + +public abstract record TopNavRenderItem(string Title) +{ + /// + /// Whether this tab is active given the current page's navigation root id. + /// Pass null when the page has no section (e.g. the homepage). + /// + public abstract bool IsActive(string? currentSectionId); +} + +/// +/// A link tab. is the ID of a single navigation root that owns this tab. +/// overrides when a tab groups multiple roots (section: +/// entries in navigation.yml). The tab is active when currentSectionId matches any owned ID. +/// External link tabs carry neither field and are never active. +/// +public record TopNavLinkItem(string Title, string Url, bool IsExternal, string? SectionId = null) : TopNavRenderItem(Title) +{ + /// When non-null, overrides for active-state matching. + public IReadOnlySet? SectionIds { get; init; } + + public override bool IsActive(string? currentSectionId) + { + if (currentSectionId is null) + return false; + if (SectionIds is not null) + return SectionIds.Contains(currentSectionId); + return SectionId is not null && currentSectionId == SectionId; + } +} + +/// +/// A dropdown tab with labelled link groups. Dropdowns have no tree-section membership and are never +/// marked active. +/// +public record TopNavDropdownItem(string Title, IReadOnlyList Groups) : TopNavRenderItem(Title) +{ + public override bool IsActive(string? currentSectionId) => false; +} + +/// A run of links inside a dropdown. A null means the links are ungrouped. +public record TopNavGroup(string? Label, IReadOnlyList Links); diff --git a/src/Elastic.Documentation.Navigation/Assembler/SiteNavigation.cs b/src/Elastic.Documentation.Navigation/Assembler/SiteNavigation.cs index 3e0ae25130..a8c6c1f99b 100644 --- a/src/Elastic.Documentation.Navigation/Assembler/SiteNavigation.cs +++ b/src/Elastic.Documentation.Navigation/Assembler/SiteNavigation.cs @@ -71,23 +71,22 @@ public SiteNavigation( } var index = items.Count; - foreach (var tocRef in siteNavigationFile.TableOfContents) + + foreach (var entry in siteNavigationFile.TableOfContents) { - var navItem = CreateSiteTableOfContentsNavigation( - tocRef, - index++, - context, - this, - null - ); - - if (navItem != null) + var tocRefs = entry is SiteSectionRef section + ? section.Children + : [entry as SiteTableOfContentsRef ?? throw new InvalidOperationException($"Unexpected entry type: {entry.GetType().Name}")]; + + foreach (var tocRef in tocRefs) { - // Every top-level navigation.yml section owns the sidebar — that is the definition of an island. - // The dropdown is how a top-level island renders its way out; see NavigationRenderModel.CreateBackLinks. - if (navItem is IAssignableIslandNavigation assignable) - assignable.IsIsland = true; - items.Add(navItem); + var navItem = CreateSiteTableOfContentsNavigation(tocRef, index++, context, this, null); + if (navItem is not null) + { + if (navItem is IAssignableIslandNavigation assignable) + assignable.IsIsland = true; + items.Add(navItem); + } } } diff --git a/src/Elastic.Documentation.Navigation/INavigationTraversable.cs b/src/Elastic.Documentation.Navigation/INavigationTraversable.cs index b9fdf597bc..3ebe8bf2dc 100644 --- a/src/Elastic.Documentation.Navigation/INavigationTraversable.cs +++ b/src/Elastic.Documentation.Navigation/INavigationTraversable.cs @@ -35,9 +35,13 @@ public string? NavigationSection get { var parents = navigationItem.GetParents(); - if (parents.Length <= 1) - return navigationItem.NavigationTitle.ToLowerInvariant(); - return parents.Reverse().Skip(1).FirstOrDefault()?.NavigationTitle.ToLowerInvariant(); + var meaningful = parents + .Reverse() + .Skip(1) + .FirstOrDefault(); + if (meaningful is not null) + return meaningful.NavigationTitle.ToLowerInvariant(); + return navigationItem.NavigationTitle.ToLowerInvariant(); } } } diff --git a/src/Elastic.Documentation.Site/Assets/main.ts b/src/Elastic.Documentation.Site/Assets/main.ts index f37b2e0744..8cfb7f371f 100644 --- a/src/Elastic.Documentation.Site/Assets/main.ts +++ b/src/Elastic.Documentation.Site/Assets/main.ts @@ -5,10 +5,10 @@ import { config } from './config' import { initCopyButton } from './copybutton' import { initHighlight } from './hljs' import { initImageCarousel } from './image-carousel' -import { initListing } from './listing' import { initMermaid } from './mermaid' import { openDetailsWithAnchor } from './open-details-with-anchor' import { initNav } from './pages-nav' +import { initSecondaryNav } from './secondary-nav' import { initSmoothScroll } from './smooth-scroll' import { initTable } from './table' import { initTabs } from './tabs' @@ -218,7 +218,6 @@ document.addEventListener('htmx:load', function () { ['initImageCarousel', initImageCarousel], ['initTable', initTable], ['initApiDocs', initApiDocs], - ['initListing', initListing], ['applyEditParam', applyEditParam], ['initCtaImpressions', initCtaImpressions], ]) @@ -237,6 +236,7 @@ function handleCtaActivation(event: MouseEvent) { logCtaEvent('cta_clicked', cta) } document.addEventListener('click', handleCtaActivation) +initSecondaryNav() // 'auxclick' with button 1 covers middle-click (open in new tab), which does NOT // fire 'click' per the DOM spec - without this those opens went untracked. Button 2 // (right-click / context menu) also fires auxclick but isn't a real engagement. diff --git a/src/Elastic.Documentation.Site/Assets/secondary-nav-dropdown.css b/src/Elastic.Documentation.Site/Assets/secondary-nav-dropdown.css new file mode 100644 index 0000000000..a782b9accb --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/secondary-nav-dropdown.css @@ -0,0 +1,87 @@ +/* + * Top-bar dropdown. Rendered for a `top_nav:` entry in navigation.yml that has + * `children:`. The label is a pure toggle, not a link: the panel lists the + * entry's children so users can jump straight to a child page. + * + * Open/close is native
. Closing on outside click or Escape is not, + * so that lives in secondary-nav.ts. + */ + +@layer components { + .secondary-nav-dropdown { + position: relative; + display: inline-flex; + align-items: center; + } + + /* The nav bar uses overflow-x:auto for horizontal tab scrolling, which + also clips vertically and would cut off an open dropdown. Let the menu + escape only while a dropdown is open. */ + .secondary-nav-scroll-container:has(.secondary-nav-dropdown[open]) { + /* !important to win over the Tailwind `overflow-x-auto` utility, + which sits in a higher cascade layer. */ + overflow: visible !important; + } + + .secondary-nav-dropdown summary { + list-style: none; + } + .secondary-nav-dropdown summary::-webkit-details-marker { + display: none; + } + + .secondary-nav-dropdown-chevron { + flex-shrink: 0; + color: var(--color-grey-60); + transition: transform 0.15s ease; + } + .secondary-nav-dropdown[open] .secondary-nav-dropdown-chevron { + transform: rotate(180deg); + } + + .secondary-nav-dropdown-menu { + position: absolute; + top: calc(100% + 4px); + left: 0; + min-width: 220px; + max-width: 320px; + z-index: 50; + display: none; + flex-direction: column; + padding: 6px 0; + background: var(--color-white); + border: 1px solid var(--color-grey-20); + border-radius: 6px; + box-shadow: 0 8px 24px rgb(0 0 0 / 0.08); + font-weight: 500; + } + .secondary-nav-dropdown[open] .secondary-nav-dropdown-menu { + display: flex; + } + + .secondary-nav-dropdown-group-label { + padding: 8px 14px 4px 14px; + font-size: 12px; + font-weight: 700; + color: var(--color-grey-80); + user-select: none; + } + .secondary-nav-dropdown-group-label:not(:first-child) { + margin-top: 4px; + border-top: 1px solid var(--color-grey-15, var(--color-grey-20)); + padding-top: 10px; + } + + .secondary-nav-dropdown-link { + display: block; + padding: 6px 14px; + font-size: 14px; + color: var(--color-ink-dark); + text-decoration: none; + transition: background-color 0.12s ease; + } + .secondary-nav-dropdown-link:hover { + background: var(--color-grey-10); + color: var(--color-blue-elastic); + } +} diff --git a/src/Elastic.Documentation.Site/Assets/secondary-nav.test.ts b/src/Elastic.Documentation.Site/Assets/secondary-nav.test.ts new file mode 100644 index 0000000000..8fe991c986 --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/secondary-nav.test.ts @@ -0,0 +1,73 @@ +import { initSecondaryNav } from './secondary-nav' + +function renderNav() { + document.body.innerHTML = ` + +

page

+ ` + return { + products: document.querySelector('#products')!, + extend: document.querySelector('#extend')!, + outside: document.querySelector('#outside')!, + } +} + +describe('initSecondaryNav', () => { + beforeAll(() => initSecondaryNav()) + + it('closes an open dropdown when clicking outside of it', () => { + const { products, outside } = renderNav() + products.open = true + + outside.dispatchEvent(new MouseEvent('click', { bubbles: true })) + + expect(products.open).toBe(false) + }) + + it('closes the other dropdowns when one is opened', () => { + const { products, extend } = renderNav() + products.open = true + + // the native
toggle opens this one, our listener closes the sibling + extend + .querySelector('summary')! + .dispatchEvent(new MouseEvent('click', { bubbles: true })) + + expect(products.open).toBe(false) + expect(extend.open).toBe(true) + }) + + it('leaves clicks inside the open panel alone so links still work', () => { + const { products } = renderNav() + products.open = true + + products + .querySelector('a')! + .dispatchEvent(new MouseEvent('click', { bubbles: true })) + + expect(products.open).toBe(true) + }) + + it('closes on Escape and returns focus to the summary', () => { + const { products } = renderNav() + products.open = true + const summary = products.querySelector('summary')! + summary.focus() + + document.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }) + ) + + expect(products.open).toBe(false) + expect(document.activeElement).toBe(summary) + }) +}) diff --git a/src/Elastic.Documentation.Site/Assets/secondary-nav.ts b/src/Elastic.Documentation.Site/Assets/secondary-nav.ts new file mode 100644 index 0000000000..a969fdf15b --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/secondary-nav.ts @@ -0,0 +1,45 @@ +/** + * Close behaviour for the top-bar dropdowns. + * + * Native
opens and closes on summary clicks, but it does not close when + * the user clicks elsewhere or presses Escape, which for a nav menu leaves a panel + * stranded over the page. These are delegated document listeners, so they survive + * the htmx body swaps that replace the nav on every navigation. + */ + +const DROPDOWN = 'details.secondary-nav-dropdown' + +function openDropdowns(): HTMLDetailsElement[] { + return Array.from( + document.querySelectorAll(`${DROPDOWN}[open]`) + ) +} + +function closeAllExcept(keep?: HTMLDetailsElement) { + for (const dropdown of openDropdowns()) { + if (dropdown !== keep) dropdown.open = false + } +} + +export function initSecondaryNav() { + document.addEventListener('click', (event: MouseEvent) => { + const target = event.target as HTMLElement | null + // A click on a summary toggles its own dropdown; only the siblings close here, + // otherwise we would fight the native toggle. + const clicked = + target?.closest(DROPDOWN) ?? undefined + closeAllExcept(clicked) + }) + + document.addEventListener('keydown', (event: KeyboardEvent) => { + if (event.key !== 'Escape') return + const open = openDropdowns() + if (open.length === 0) return + const active = ( + document.activeElement as HTMLElement | null + )?.closest(DROPDOWN) + closeAllExcept() + // Focus would otherwise be lost on the removed panel, stranding keyboard users. + active?.querySelector('summary')?.focus() + }) +} diff --git a/src/Elastic.Documentation.Site/Assets/styles.css b/src/Elastic.Documentation.Site/Assets/styles.css index c3599fa395..d70879b8bd 100644 --- a/src/Elastic.Documentation.Site/Assets/styles.css +++ b/src/Elastic.Documentation.Site/Assets/styles.css @@ -22,6 +22,7 @@ @import './markdown/image-carousel.css'; @import './markdown/hr.css'; @import './modal.css'; +@import './secondary-nav-dropdown.css'; @import './archive.css'; @import './markdown/stepper.css'; @import './markdown/button.css'; diff --git a/src/Elastic.Documentation.Site/Layout/_SecondaryNav.cshtml b/src/Elastic.Documentation.Site/Layout/_SecondaryNav.cshtml index 10ce2f50e9..f0714b58a1 100644 --- a/src/Elastic.Documentation.Site/Layout/_SecondaryNav.cshtml +++ b/src/Elastic.Documentation.Site/Layout/_SecondaryNav.cshtml @@ -1,40 +1,110 @@ +@using Elastic.Documentation.Configuration.Toc @inherits RazorSlice +@{ + var topNav = Model.TopNav; + // Active tab: compare the current page's NavigationRoot.Id against each tab's SectionId. + // The site root (SiteNavigation) has an Id that matches no tab, so homepage gets no active tab. + var currentSectionId = Model.CurrentNavigationItem.NavigationRoot?.Id; +}
diff --git a/src/Elastic.Documentation.Site/_ViewModels.cs b/src/Elastic.Documentation.Site/_ViewModels.cs index 7bbc4db766..3419192aaf 100644 --- a/src/Elastic.Documentation.Site/_ViewModels.cs +++ b/src/Elastic.Documentation.Site/_ViewModels.cs @@ -52,6 +52,12 @@ public record GlobalLayoutViewModel /// Breadcrumb trail for codex sub-header (Home / Group / Docset). public IReadOnlyList? CodexBreadcrumbs { get; init; } + /// + /// The configured top navigation for assembler builds. When null the secondary nav renders + /// its built-in links instead. + /// + public TopNavRenderModel? TopNav { get; init; } + /// /// When the current page is a hidden nav item (e.g. an individual detection rule page), /// the URL of its nearest visible ancestor. The client uses this to highlight the correct diff --git a/src/Elastic.Markdown/HtmlWriter.cs b/src/Elastic.Markdown/HtmlWriter.cs index 40cd683c0e..62fe04ebbd 100644 --- a/src/Elastic.Markdown/HtmlWriter.cs +++ b/src/Elastic.Markdown/HtmlWriter.cs @@ -196,6 +196,7 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc GoogleTagManager = DocumentationSet.Context.GoogleTagManager, Optimizely = DocumentationSet.Context.Optimizely, Features = DocumentationSet.Configuration.Features, + TopNav = DocumentationSet.Context.TopNav, StaticFileContentHashProvider = StaticFileContentHashProvider, ReportIssueUrl = reportUrl, CurrentVersion = currentBaseVersion, diff --git a/src/Elastic.Markdown/Page/Index.cshtml b/src/Elastic.Markdown/Page/Index.cshtml index f79b0b1853..4db56ab707 100644 --- a/src/Elastic.Markdown/Page/Index.cshtml +++ b/src/Elastic.Markdown/Page/Index.cshtml @@ -37,6 +37,7 @@ GoogleTagManager = Model.GoogleTagManager, Optimizely = Model.Optimizely, Features = Model.Features, + TopNav = Model.TopNav, StaticFileContentHashProvider = Model.StaticFileContentHashProvider, ReportIssueUrl = Model.ReportIssueUrl, Breadcrumbs = Model.Breadcrumbs, diff --git a/src/Elastic.Markdown/Page/IndexViewModel.cs b/src/Elastic.Markdown/Page/IndexViewModel.cs index e2644f6188..984a4f4786 100644 --- a/src/Elastic.Markdown/Page/IndexViewModel.cs +++ b/src/Elastic.Markdown/Page/IndexViewModel.cs @@ -86,6 +86,9 @@ public class IndexViewModel /// Codex sub-header breadcrumb trail (Home / Group / Docset). public IReadOnlyList? CodexBreadcrumbs { get; set; } + /// The configured site-wide top navigation. Null outside assembler builds. + public TopNavRenderModel? TopNav { get; init; } + /// When set, the page performs a client-side redirect to this URL (used for alias pages). public string? RedirectUrl { get; init; } diff --git a/src/services/Elastic.Documentation.Assembler/AssembleSources.cs b/src/services/Elastic.Documentation.Assembler/AssembleSources.cs index 0b3d4cdd63..cc32914279 100644 --- a/src/services/Elastic.Documentation.Assembler/AssembleSources.cs +++ b/src/services/Elastic.Documentation.Assembler/AssembleSources.cs @@ -32,6 +32,8 @@ public class AssembleSources public PublishEnvironmentUriResolver UriResolver { get; } + public ICrossLinkResolver CrossLinkResolver { get; } + public static async Task AssembleAsync( ILoggerFactory logFactory, AssembleContext context, @@ -107,6 +109,7 @@ IReadOnlySet availableExporters NavigationTocMappings = navigationTocMappings; LegacyUrlMappings = legacyUrlMappings; UriResolver = uriResolver; + CrossLinkResolver = crossLinkResolver; AssembleContext = assembleContext; AssembleSets = checkouts .Where(c => c.Repository is { Skip: false }) @@ -179,11 +182,15 @@ static void ReadBlock( string? repository = null; string? source = null; string? pathPrefix = null; + var isSection = false; foreach (var entry in tocEntry.Children) { var key = ((YamlScalarNode)entry.Key).Value; switch (key) { + case "section": + isSection = true; + break; case "toc": source = reader.ReadString(entry); if (source.AsSpan().IndexOf("://") == -1) @@ -212,7 +219,20 @@ static void ReadBlock( } if (source is null) + { + // section: entries have no source; descend into their children so the children's + // sources are registered in NavigationTocMappings. + if (isSection) + { + foreach (var entry in tocEntry.Children) + { + var key = ((YamlScalarNode)entry.Key).Value; + if (key == "children") + ReadTocBlocks(entries, reader, entry, parent, depth, topLevelSource, parentSource); + } + } return; + } source = source.EndsWith("://", StringComparison.OrdinalIgnoreCase) ? source : source.TrimEnd('/') + "/"; if (!Uri.TryCreate(source, UriKind.Absolute, out var sourceUri)) diff --git a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs index 610d47272b..dba0646009 100644 --- a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs +++ b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs @@ -114,6 +114,10 @@ Cancel ctx if (!SiteNavigationFile.ValidatePathPrefixes(assembleContext.Collector, siteNavigationFile, navigationFileInfo) || assembleContext.Collector.Errors > 0) return false; + var topNav = SectionTopNavBuilder.Build(navigation, siteNavigationFile); + foreach (var set in assembleSources.AssembleSets.Values) + set.BuildContext.TopNav = topNav; + var pathProvider = new GlobalNavigationPathProvider(navigation, assembleSources, assembleContext); var htmlWriter = new GlobalNavigationHtmlWriter(logFactory, navigation, collector); var legacyPageChecker = new LegacyPageService(logFactory); diff --git a/src/services/Elastic.Documentation.Assembler/Navigation/GlobalNavigationHtmlWriter.cs b/src/services/Elastic.Documentation.Assembler/Navigation/GlobalNavigationHtmlWriter.cs index 028d4abc67..9700aede33 100644 --- a/src/services/Elastic.Documentation.Assembler/Navigation/GlobalNavigationHtmlWriter.cs +++ b/src/services/Elastic.Documentation.Assembler/Navigation/GlobalNavigationHtmlWriter.cs @@ -46,7 +46,7 @@ private NavigationRenderModel CreateNavigationModel(INodeNavigationItem