diff --git a/docs/data/openapi/api-explorer.md b/docs/data/openapi/api-explorer.md index 92e39e23e6..c23cc9debd 100644 --- a/docs/data/openapi/api-explorer.md +++ b/docs/data/openapi/api-explorer.md @@ -10,6 +10,8 @@ Authored API Markdown pages can use [`meta_title`](../../syntax/frontmatter.md#m The assembler also writes a combined **Elastic APIs** page at `/docs/api/`, on its own layout with no API sidebar. The cards match the ones on the [docs landing page](https://www.elastic.co/docs): a rounded box with the product icon, a bold title, and a short description. The whole card is one link to the API. The title sits on a full-width band with one line of text under it that says which deployments the APIs cover. Two small labels group the APIs into two full-width rows. **Elastic Stack** shows Elasticsearch, Kibana and Logstash as three equal large cards, with the Serverless variants of Elasticsearch and Kibana as small compact cards directly under their parents. **Orchestration** shows the five Elastic Cloud APIs three across, in the same compact card as the Serverless variants but without the attachment. An API that is in neither group follows them, sorted by title. Each API appears once. If only one group has APIs, the label is dropped. If fewer than two of Elasticsearch, Kibana and Logstash are present, the group has no larger cards. +Under the line of text sits the same finder as in the API sidebar, labelled **Press ❲/❳ to find API**. It looks up operations across every API, so you can jump to an endpoint without knowing which product it belongs to. Press / anywhere on the page, outside a text field, to jump to it. Like the sidebar finder, it is omitted on isolated and air-gapped builds. + The cards for APIs that are in neither group show the API's deployments as small pills taken from `catalog.categories`. They use the same look as `applies_to` badges and the same names (**Self-managed**, **ECE**, **ECH**, **Serverless**), but show the name only: the catalog knows the deployment, not a lifecycle or version. The grouped cards leave them out, and an API with no categories shows no pills. The description is a short plain-text summary of `info.description` (the first paragraph with Markdown removed). JSON, YAML, and Markdown downloads stay on each API's landing page and in the `/docs/api.md` export. The API title comes from `info.title` in the spec, so fix naming and description copy in the spec. @@ -152,7 +154,7 @@ Each product key in the `api:` block must have **exactly one** entry, with **exa `spec:`. The build fails if a product sequence is empty or has more than one entry. Multiple specs per product are not currently supported. -On assembler builds a bar under the site header says which API you are reading: `[logo] Elasticsearch API ⇅ [main ▾]`. The API name opens a switcher that lists every API whose spec loaded (each with its logo, plus an **All APIs** link back to the catalog), and the version picker sits right beside it. Each switcher row uses that spec's title (`info.title`), the same text as the landing page heading. On the right, the **Download OpenAPI spec** button downloads the whole spec as JSON or YAML; on narrow screens it shows only its icon. The page itself starts with the full breadcrumb, which begins at Docs. The bar looks the same whether or not `navigation-preview` is on. The catalog page has no API, so it has no bar. +On assembler builds a bar under the site header says which API you are reading: `[logo] Elasticsearch API ⇅ [main ▾]`. The API name opens a switcher that lists every API whose spec loaded (each with its logo, plus an **All APIs** link back to the catalog), and the version picker sits right beside it. Each switcher row uses that spec's title (`info.title`), the same text as the landing page heading. On the right, the **Download OpenAPI spec** button downloads the whole spec as JSON or YAML; on narrow screens it shows only its icon. The page itself starts with the full breadcrumb, which begins at Docs. The bar looks the same whether or not `navigation-preview` is on. The catalog page has no API, so it has no bar; its finder sits in the title band instead. The left navigation starts with a finder, labelled **Press ❲/❳ to find API**. It looks up operations by title across every API, not only the one you are reading, and shows the matches in a list; it does not filter the navigation tree. It is not the site-wide search. Press / anywhere on the page, outside a text field, to jump to the box, as on GitHub. The shortcut is off on narrow screens, where the navigation sits in a drawer. Isolated and air-gapped builds omit it. Isolated builds have no site header and no bar, so they put the API name, switcher and version picker at the top of the left navigation instead. Markdown docs pages do not get the filter box back. diff --git a/src/Elastic.ApiExplorer/Landing/ApiCatalogView.cshtml b/src/Elastic.ApiExplorer/Landing/ApiCatalogView.cshtml index ccbb3c3493..a6dbf9ff42 100644 --- a/src/Elastic.ApiExplorer/Landing/ApiCatalogView.cshtml +++ b/src/Elastic.ApiExplorer/Landing/ApiCatalogView.cshtml @@ -18,6 +18,10 @@

Reference

@ApiCatalog.PageTitle

@ApiCatalog.PageLede

+ @if (LayoutModel.ShowJumpToPage) + { +
+ } return Task.CompletedTask; @@ -31,6 +35,10 @@

@ApiCatalog.PageTitle

@ApiCatalog.PageLede

+ @if (LayoutModel.ShowJumpToPage) + { +
+ } diff --git a/src/Elastic.Documentation.Site/Assets/api-docs.css b/src/Elastic.Documentation.Site/Assets/api-docs.css index bf9e70ba5d..19f39ad9b5 100644 --- a/src/Elastic.Documentation.Site/Assets/api-docs.css +++ b/src/Elastic.Documentation.Site/Assets/api-docs.css @@ -3199,6 +3199,37 @@ button.example-response-tab:focus-visible { text-wrap: balance; } +/* The finder is the same box as the API sidebar's, so it looks up operations across every API. It sits + under the lede, as wide as the lede, and its results open below it. Both heroes use it, and the + navigation-preview one sits outside `.api-catalog`, so the rule stands alone. */ +.api-catalog-finder { + max-width: 36rem; + margin: 20px auto 0; + text-align: left; +} + +/* The box is added by script. min-height is its height (20px line, 12px padding, 2px border), so what sits + below it, the sidebar tree or the catalog cards, does not jump when it loads. */ +navigation-search[variant='sidebar'] { + display: block; + min-height: 34px; +} + +.api-catalog-finder navigation-search { + width: 100%; +} + +/* EUI caps an input popover at 400px unless it is told to be full width. The cap left the box at the left + edge of the finder instead of filling it. */ +.api-catalog-finder .euiInputPopover { + max-width: none; +} + +/* The navigation-preview hero is left-aligned, so the finder lines up with its title and lede. */ +.landing__hero-text .api-catalog-finder { + margin-inline: 0; +} + @media (max-width: 640px) { .api-catalog .hub-hero.api-catalog-hero { padding: 36px 0 32px; diff --git a/tests/Elastic.ApiExplorer.Tests/OpenApiGeneratorCatalogSplitTests.cs b/tests/Elastic.ApiExplorer.Tests/OpenApiGeneratorCatalogSplitTests.cs index 386640b9df..41a935c203 100644 --- a/tests/Elastic.ApiExplorer.Tests/OpenApiGeneratorCatalogSplitTests.cs +++ b/tests/Elastic.ApiExplorer.Tests/OpenApiGeneratorCatalogSplitTests.cs @@ -396,7 +396,7 @@ public async Task GenerateCatalog_CategoriesDeclared_RendersOneListWithDeploymen } [Test] - public async Task GenerateCatalog_RendersTheTitleBandOutsideTheContainerAndNoSearchField() + public async Task GenerateCatalog_RendersTheTitleBandOutsideTheContainer() { var outputRoot = Path.Join(Paths.WorkingDirectoryRoot.FullName, $"api-catalog-split-{Guid.NewGuid():N}"); var context = CreateGenerateContext(outputRoot); @@ -423,9 +423,38 @@ await generator.GenerateCatalog( html.IndexOf("id=\"content-container\"", StringComparison.Ordinal), "the band is rendered outside the width-limited container" ); - html.Should().NotContain("navigation-search", "the catalog has no search field"); } + [Test] + [Arguments(BuildType.Assembler, true)] + [Arguments(BuildType.Isolated, false)] + public async Task GenerateCatalog_Finder_AppearsOnlyWhereTheSidebarHasOne(BuildType buildType, bool expected) + { + var outputRoot = Path.Join(Paths.WorkingDirectoryRoot.FullName, $"api-catalog-split-{Guid.NewGuid():N}"); + var context = CreateGenerateContext(outputRoot, buildType: buildType); + using var versionIndexClient = new VersionIndexClient(BaseUri, MultiVersionHandler(), sleep: (_, _) => Task.CompletedTask); + var generator = new OpenApiGenerator(NullLoggerFactory.Instance, context, NoopMarkdownStringRenderer.Instance, versionIndexClient); + + await generator.GenerateCatalog( + [new("elasticsearch", "Elasticsearch", "/docs/api/doc/elasticsearch", "elasticsearch")], + TestContext.Current!.Execution.CancellationToken + ); + + var html = await context + .WriteFileSystem + .File + .ReadAllTextAsync(Path.Join(outputRoot, "api", "index.html"), TestContext.Current!.Execution.CancellationToken); + if (expected) + { + html.Should().Contain(""); + Count(html, " html.Split(value).Length - 1; + [Test] public async Task GenerateCatalog_CloudHostedAndEnterprise_ShowCloudMarksNotTheGenericGlyph() { @@ -450,7 +479,12 @@ public async Task GenerateCatalog_CloudHostedAndEnterprise_ShowCloudMarksNotTheG html.Should().NotContain("M13.5 5 10.5 19", "that is the generic fallback glyph"); } - private static BuildContext CreateGenerateContext(string outputRoot, string? docsetYaml = null, string? displayName = null) + private static BuildContext CreateGenerateContext( + string outputRoot, + string? docsetYaml = null, + string? displayName = null, + BuildType buildType = BuildType.Isolated + ) { var collector = new DiagnosticsCollector([]); var stack = TestHelpers.CreateStackVersionsConfiguration(currentMajor: 9); @@ -501,7 +535,7 @@ private static BuildContext CreateGenerateContext(string outputRoot, string? doc ), configurationContext ) - { UrlPathPrefix = "docs" }; + { UrlPathPrefix = "docs", BuildType = buildType }; } private static OpenApiDocument SpecDocument(string title) =>