diff --git a/docs/_docset.yml b/docs/_docset.yml index a09f0def2..ff97ac2ba 100644 --- a/docs/_docset.yml +++ b/docs/_docset.yml @@ -149,3 +149,5 @@ toc: - folder: baz children: - file: qux.md + - link: https://github.com/elastic/docs-builder + title: GitHub repository \ No newline at end of file diff --git a/docs/configure/content-set/navigation.md b/docs/configure/content-set/navigation.md index 318a32804..2f70da400 100644 --- a/docs/configure/content-set/navigation.md +++ b/docs/configure/content-set/navigation.md @@ -148,6 +148,39 @@ It [may be linked to locally however](../../developer-notes.md) #### Nesting `toc` +The `toc` key can include nested `toc.yml` files. + +The following example includes two sub-`toc.yml` files located in directories named `elastic-basics` and `solutions`: + +```yml +toc: + - file: index.md + - toc: elastic-basics + - toc: solutions +``` + +#### External links + +You can include links to external websites directly in your navigation tree. Use the `link` key with a valid URL, and provide a human-friendly `title`. + +```yaml +toc: + - link: https://elastic.co + title: Elastic Website + - toc: getting-started + - link: https://github.com/elastic/docs-builder + title: Docs Builder Repository +``` + +Docs-builder renders external links with an icon, opens them in a new tab, and adds `rel="noopener noreferrer"` for security. + +Best practices: + +* Use external links sparingly in your primary navigation, placing them near the bottom of a section when possible. +* Provide clear titles that indicate the link leads to an external resource. +* Periodically verify that external URLs remain valid, and update them if destinations change. + + The `toc` key can include nested `toc.yml` files. The following example includes two sub-`toc.yml` files located in directories named `elastic-basics` and `solutions`: diff --git a/src/Elastic.Documentation.Configuration/Builder/TableOfContentsConfiguration.cs b/src/Elastic.Documentation.Configuration/Builder/TableOfContentsConfiguration.cs index 907ee8e6b..3120d1c75 100644 --- a/src/Elastic.Documentation.Configuration/Builder/TableOfContentsConfiguration.cs +++ b/src/Elastic.Documentation.Configuration/Builder/TableOfContentsConfiguration.cs @@ -130,6 +130,8 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV { string? file = null; string? folder = null; + string? link = null; + string? linkTitle = null; string[]? detectionRules = null; TableOfContentsConfiguration? toc = null; var detectionRulesFound = false; @@ -148,6 +150,12 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV hiddenFile = key == "hidden"; file = ReadFile(reader, entry, parentPath); break; + case "link": + link = reader.ReadString(entry); + break; + case "title": + linkTitle = reader.ReadString(entry); + break; case "folder": folder = ReadFolder(reader, entry, parentPath); parentPath += $"{Path.DirectorySeparatorChar}{folder}"; @@ -199,6 +207,15 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV return [new FileReference(this, path, hiddenFile, children ?? [])]; } + if (link is not null) + { + // external links cannot have children + if (children is not null) + reader.EmitWarning("'link' entries may not contain 'children'", tocEntry); + + return [new LinkReference(this, link, linkTitle ?? link)]; + } + if (folder is not null) { if (children is null) diff --git a/src/Elastic.Documentation.Configuration/TableOfContents/ITocItem.cs b/src/Elastic.Documentation.Configuration/TableOfContents/ITocItem.cs index a5f745150..e4a3d4599 100644 --- a/src/Elastic.Documentation.Configuration/TableOfContents/ITocItem.cs +++ b/src/Elastic.Documentation.Configuration/TableOfContents/ITocItem.cs @@ -27,15 +27,15 @@ public record TocReference(Uri Source, ITableOfContentsScope TableOfContentsScop /// A phantom table of contents is a table of contents that is not rendered in the UI but is used to generate the TOC. /// This should be used sparingly and needs explicit configuration in navigation.yml. /// It's typically used for container TOC that holds various other TOC's where its children are rehomed throughout the navigation. - /// Examples of phantom toc's: - /// - /// - toc: elasticsearch://reference - /// - toc: docs-content:// - /// - /// Because navigation.yml does exhaustive checks to ensure all toc.yml files are referenced, marking these containers as phantoms - /// ensures that these skip validation checks - /// /// public bool IsPhantom { get; init; } } +/// +/// Represents an external link in the table of contents. +/// +/// Scope this link belongs to. +/// Absolute URL of the external resource. +/// Display title for the link. +public record LinkReference(ITableOfContentsScope TableOfContentsScope, string Url, string Title) : ITocItem; + diff --git a/src/Elastic.Documentation.Site/Assets/markdown/typography.css b/src/Elastic.Documentation.Site/Assets/markdown/typography.css index 947c11e5a..450c02c95 100644 --- a/src/Elastic.Documentation.Site/Assets/markdown/typography.css +++ b/src/Elastic.Documentation.Site/Assets/markdown/typography.css @@ -49,11 +49,14 @@ } a { - @apply font-body text-blue-elastic hover:text-blue-elastic-100 underline; - &[target='_blank']::after { @apply ml-0.5; content: url("data:image/svg+xml; utf8, "); } } } + +#pages-nav a[target='_blank']:after { + content: url("data:image/svg+xml; utf8, "); + margin-left: 0.5em; +} diff --git a/src/Elastic.Documentation.Site/Elastic.Documentation.Site.csproj b/src/Elastic.Documentation.Site/Elastic.Documentation.Site.csproj index c8222af4c..82275cf59 100644 --- a/src/Elastic.Documentation.Site/Elastic.Documentation.Site.csproj +++ b/src/Elastic.Documentation.Site/Elastic.Documentation.Site.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -15,7 +15,7 @@ - + diff --git a/src/Elastic.Documentation.Site/Navigation/ExternalLinkNavigationItem.cs b/src/Elastic.Documentation.Site/Navigation/ExternalLinkNavigationItem.cs new file mode 100644 index 000000000..50eff22a9 --- /dev/null +++ b/src/Elastic.Documentation.Site/Navigation/ExternalLinkNavigationItem.cs @@ -0,0 +1,26 @@ +// 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 + +using Elastic.Documentation.Site.Navigation; + +namespace Elastic.Documentation.Site.Navigation; + +/// +/// Navigation item representing an external URL in the TOC. +/// +public record ExternalLinkNavigationItem(string ExternalUrl, string Title, INodeNavigationItem Parent, IRootNavigationItem NavigationRoot) + : INavigationItem +{ + public INodeNavigationItem? Parent { get; set; } = Parent; + + public IRootNavigationItem NavigationRoot { get; } = NavigationRoot; + + public string Url => ExternalUrl; + + public string NavigationTitle => Title; + + public int NavigationIndex { get; set; } + + public bool Hidden => false; +} diff --git a/src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml b/src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml index 19fe3e250..129a66cf5 100644 --- a/src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml +++ b/src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml @@ -1,4 +1,5 @@ @using Elastic.Documentation.Site.Navigation + @inherits RazorSlice @{ var isTopLevel = Model.Level == 0; @@ -60,7 +61,7 @@ // Only render children if we're within the allowed level depth // MaxLevel of -1 means render all levels bool shouldRenderChildren = Model.MaxLevel == -1 || Model.Level < (Model.MaxLevel); -