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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/_docset.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
project: 'doc-builder'
max_toc_depth: 2
dev_docs: true
Expand Down Expand Up @@ -146,6 +146,7 @@
- file: tables.md
- file: tabs.md
- file: titles.md
- file: whats-new.md

# Examples
- folder: examples
Expand Down
4 changes: 4 additions & 0 deletions docs/examples/products/docs-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ steps:
description: Check links, syntax, and frontmatter before you open a pull request.
:::

:::{whats-new}
:product: docs-builder
:::

::::{card-group}
:title: Get hands-on
:id: hands-on
Expand Down
49 changes: 49 additions & 0 deletions docs/hub-whats-new.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Content for the {whats-new} panel on hub pages, keyed by product.
#
# This file lives at the root of the documentation set, alongside changelog.yml and
# redirects.yml, so a writer edits it without touching the build tool. A hub page renders
# a panel from it with one line: `:product: <key>`.
#
# The directive reads this file from the current documentation set only. It cannot render
# another repository's panel.
products:
docs-builder:
title: What's new in docs-builder
id: whats-new
intro: Recent additions to the toolchain and the syntax it understands.
# More than one release stream can be listed here.
release-links:
- label: Release notes
url: /data/release-notes/index.md
- label: Changelog reference
url: /data/release-notes/overview.md
upgrade-link:
label: Upgrade docs-builder
url: /getting-started/installation.md
items:
- title: Hub pages
description: A product-scoped landing page composed entirely from directives.
link: /syntax/hub-pages.md
date: Aug 2026
tag: Syntax
featured: true
- title: Explore sections
description: Collapse a long link list into a stack of accordions.
link: /syntax/explore.md
date: Aug 2026
tag: Syntax
- title: Get started sections
description: One opinionated onboarding path, with a copyable install command.
link: /syntax/get-started.md
date: Aug 2026
tag: Syntax
- title: Link cards
description: A card with a title, a description, and a validated link list.
link: /syntax/link-card.md
date: Aug 2026
tag: Syntax
- title: Card groups
description: Group related cards under a heading, or as one accordion.
link: /syntax/card-group.md
date: Aug 2026
tag: Syntax
1 change: 1 addition & 0 deletions docs/syntax/hub-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Write both deliberately. The search body indexes the hero title and description
| [`{card-group}`](card-group.md) | Section heading and card grid. Renders as an accordion inside `{explore}`. |
| [`{link-card}`](link-card.md) | One card: title, description, and a list of links. Renders as a link column inside `{explore}`. |
| [`{get-started}`](get-started.md) | Onboarding funnel. An install command, a tutorial link, and numbered steps. |
| [`{whats-new}`](whats-new.md) | Recency panel. Dated highlight cards, authored once in `hub-whats-new.yml`. |
| [`{explore}`](explore.md) | The browse-everything section. A stack of collapsible accordions. |

## Page skeleton
Expand Down
81 changes: 81 additions & 0 deletions docs/syntax/whats-new.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# What's new

A recency panel for a [hub page](hub-pages.md). A reader who bookmarks a hub wants a quick answer to "what changed recently" without hunting through release notes.

See the [docs-builder documentation hub](../examples/products/docs-builder.md) for a rendered panel.

## Basic

The common case is one line:

```markdown
:::{whats-new}
:product: docs-builder
:::
```

`:product:` looks the key up in `hub-whats-new.yml` at the root of the documentation set. The content is authored once there and every page that names the same product renders the same panel. One edit updates them all.

## Where the content lives

`hub-whats-new.yml` sits beside `changelog.yml` and `redirects.yml`, at the root of the content repository rather than in the build tool. A writer edits the panel without opening docs-builder, and without waiting for a docs-builder release.

```yaml
products:
docs-builder:
title: What's new in docs-builder
id: whats-new
intro: Recent additions to the toolchain.
release-links:
- label: View release notes
url: /data/release-notes/index.md
items:
- title: Hub pages
description: A product-scoped landing page composed entirely from directives.
link: /syntax/hub-pages.md
date: Aug 2026
tag: Syntax
featured: true
```

| Field | Notes |
|---|---|
| `title` | H2 heading. |
| `id` | Section anchor. Use `whats-new` so `{hero}`'s secondary action can jump to it. |
| `intro` | One-line lead. |
| `release-links` | Links to the full release notes, shown beside the heading. List more than one when a product has several release streams. |
| `upgrade-link` | An upgrade prompt below the grid. Takes `label` and `url`. |
| `items` | The highlight cards. |

Each item takes a `title`, a `description`, a `link`, a `date` and a `tag`. Mark one item `featured: true` to span two columns.

The `date` renders as you write it. Use sentence case, for example `Aug 2026`.

Every field except `title` is optional. The example file uses each one once, so you can start from it and delete what you do not need.

## Inline body

Omit `:product:` and give the directive the same schema as a YAML body, for a one-off panel that does not belong in the shared file:

```markdown
:::{whats-new}
title: What's new
items:
- title: Hub pages
description: A product-scoped landing page.
link: /syntax/hub-pages.md
date: Aug 2026
:::
```

## Scope limit

The directive reads the file in the current documentation set. It cannot render another repository's panel.

That is not a syntax gap. Cross-link resolution maps pages through the link index, and a YAML data file is not a page. In an isolated build the other repository is not checked out, so there is no file to read at all.

Every hub page lives in the same repository as its content file, so this costs nothing today.

## Links

Every `release-links[].url`, `upgrade-link.url`, and `items[].link` validates at build time, using the same forms as [`{link-card}`](link-card.md#links).
159 changes: 159 additions & 0 deletions src/Elastic.Documentation.Site/Assets/markdown/hub.css
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,165 @@
color: var(--color-blue-elastic-100, #0b64dd);
}

/* What's new ------------------------------------------------------- */
.hub-whats-new {
@apply mx-auto w-full max-w-5xl;
margin-bottom: 56px;
scroll-margin-top: 120px;
}
.hub-whats-new .hub-wn-header {
@apply mb-6 flex flex-wrap items-end justify-between gap-4;
}
.hub-whats-new .hub-wn-title {
font-size: var(--text-2xl);
font-weight: 700;
color: var(--color-ink-dark);
line-height: 1.2;
margin: 0;
}
.hub-whats-new .hub-wn-intro {
color: var(--color-ink-light);
margin-top: 8px;
max-width: 60ch;
}
.hub-whats-new .hub-wn-rn {
@apply flex flex-shrink-0 flex-wrap items-center gap-x-2 gap-y-1;
}
.hub-whats-new .hub-wn-footer {
@apply mt-4 flex flex-wrap items-center justify-center gap-x-2 gap-y-1;
}
.hub-whats-new .hub-wn-footer-text {
font-size: var(--text-base);
color: var(--color-ink-light);
}
.hub-whats-new .hub-wn-upgrade {
@apply inline-flex items-center gap-1.5;
font-size: var(--text-base);
font-weight: 600;
color: var(--color-blue-elastic-100);
text-decoration: none;
}
.hub-whats-new .hub-wn-upgrade:hover,
.hub-whats-new .hub-wn-upgrade:focus-visible {
text-decoration: underline;
}
.hub-whats-new .hub-wn-upgrade svg {
flex-shrink: 0;
}
.hub-whats-new .hub-wn-rn-label {
font-size: var(--text-base);
font-weight: 600;
color: var(--color-ink-light);
}
.hub-whats-new .hub-wn-rn-list {
@apply m-0 flex flex-wrap items-center p-0;
list-style: none;
}
.hub-whats-new .hub-wn-rn-list li {
@apply inline-flex items-center;
}
.hub-whats-new .hub-wn-rn-list li:not(:last-child)::after {
content: '·';
color: var(--color-grey-40);
margin: 0 10px;
font-weight: 700;
}
.hub-whats-new .hub-wn-rn-link {
font-size: var(--text-base);
font-weight: 600;
color: var(--color-blue-elastic-100);
text-decoration: none;
}
.hub-whats-new .hub-wn-rn-link:hover,
.hub-whats-new .hub-wn-rn-link:focus-visible {
text-decoration: underline;
}

/* What's new card grid */
.hub-whats-new .hub-wn-grid {
@apply m-0 grid list-none p-0;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.hub-whats-new .hub-wn-card {
margin: 0;
}
.hub-whats-new .hub-wn-card-featured {
grid-column: span 2;
}
.hub-whats-new .hub-wn-card-link {
@apply flex h-full flex-col gap-2.5;
padding: 18px 20px;
background: var(--color-white);
border: 1px solid var(--color-grey-20);
border-radius: 14px;
text-decoration: none;
color: inherit;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
}
/* Same hover treatment as every other card that is itself a link. The pale blue this
replaced read at 2.12:1 against white, so the state was barely visible. */
.hub-whats-new a.hub-wn-card-link:hover,
.hub-whats-new .hub-wn-card-link:focus-visible {
border-color: var(--color-grey-80);
box-shadow: 0 2px 10px rgb(0 0 0 / 0.06);
}
.hub-whats-new .hub-wn-card-meta {
@apply flex items-center justify-between gap-2;
font-size: var(--text-sm);
}
.hub-whats-new .hub-wn-card-meta-left {
@apply inline-flex items-center gap-2;
}
/* The date is authored, so it keeps the casing the writer chose. Forcing
uppercase shouts, and it mangles a month name in any language. */
.hub-whats-new .hub-wn-card-date {
font-weight: 600;
color: var(--color-ink-light);
letter-spacing: 0.03em;
}
.hub-whats-new .hub-wn-card-tag {
color: var(--color-grey-80, #69707d);
font-weight: 500;
}
.hub-whats-new .hub-wn-card-title {
font-size: var(--text-base);
font-weight: 700;
color: var(--color-ink-dark);
line-height: 1.3;
margin: 0;
}
.hub-whats-new .hub-wn-card-featured .hub-wn-card-title {
font-size: var(--text-lg);
}
.hub-whats-new .hub-wn-card-desc {
font-size: var(--text-base);
color: var(--color-ink-light);
line-height: 1.45;
margin: 0;
}
.hub-whats-new .hub-wn-card-more {
@apply mt-auto inline-flex items-center gap-1.5;
font-size: var(--text-base);
font-weight: 600;
color: var(--color-blue-elastic-100);
}
@media (max-width: 900px) {
.hub-whats-new .hub-wn-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.hub-whats-new .hub-wn-grid {
grid-template-columns: 1fr;
}
.hub-whats-new .hub-wn-card-featured {
grid-column: span 1;
}
}

/* Zone (section heading) ------------------------------------------ */
.hub-zone {
@apply mx-auto w-full max-w-5xl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ protected override DirectiveBlock CreateFencedBlock(BlockProcessor processor)
if (info.IndexOf("{get-started}") > 0)
return new GetStartedBlock(this, context);

if (info.IndexOf("{whats-new}") > 0)
return new WhatsNewBlock(this, context);

if (info.IndexOf("{agent-skill}") > 0)
return new AgentSkillBlock(this, context);

Expand Down
14 changes: 14 additions & 0 deletions src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ protected override void Write(HtmlRenderer renderer, DirectiveBlock directiveBlo
case GetStartedBlock getStartedBlock:
WriteGetStarted(renderer, getStartedBlock);
return;
case WhatsNewBlock whatsNewBlock:
WriteWhatsNew(renderer, whatsNewBlock);
return;
case PageCardBlock pageCardBlock:
WritePageCard(renderer, pageCardBlock);
return;
Expand Down Expand Up @@ -270,6 +273,17 @@ private static void WriteLinkCard(HtmlRenderer renderer, LinkCardBlock block)
RenderRazorSlice(slice, renderer);
}

private static void WriteWhatsNew(HtmlRenderer renderer, WhatsNewBlock block)
{
var slice = WhatsNewView.Create(new WhatsNewViewModel
{
DirectiveBlock = block,
Data = block.Data,
SitePathPrefix = block.Build.UrlPathPrefix
});
RenderRazorSlice(slice, renderer);
}

private static void WriteGetStarted(HtmlRenderer renderer, GetStartedBlock block)
{
var data = block.Data;
Expand Down
Loading
Loading