diff --git a/docs/_docset.yml b/docs/_docset.yml
index 2f45d80c6a..0dd5a42d69 100644
--- a/docs/_docset.yml
+++ b/docs/_docset.yml
@@ -147,6 +147,7 @@ toc:
- file: math.md
- file: diagrams.md
- file: lists.md
+ - file: task-lists.md
- file: line_breaks.md
- file: links.md
- file: list-sub-pages.md
diff --git a/docs/syntax/index.md b/docs/syntax/index.md
index 8ea18eac08..de6b664fa6 100644
--- a/docs/syntax/index.md
+++ b/docs/syntax/index.md
@@ -23,7 +23,6 @@ V3 supports some GitHub Flavored Markdown extensions:
- Strikethrough with `~~text~~` (renders as ~~text~~)
**Not supported:**
-- Task lists
- Automatic URL linking: https://www.elastic.co
- Links must use standard Markdown syntax: [Elastic](https://www.elastic.co)
- Using a subset of HTML
\ No newline at end of file
diff --git a/docs/syntax/task-lists.md b/docs/syntax/task-lists.md
new file mode 100644
index 0000000000..d16b97f5da
--- /dev/null
+++ b/docs/syntax/task-lists.md
@@ -0,0 +1,23 @@
+# Task Lists
+
+Task lists let you render checkboxes in documentation. Use `- [ ]` for unchecked and `- [x]` for checked items.
+
+## Basic Task List
+
+- [x] Completed task
+- [ ] Pending task
+- [ ] Another pending task
+
+## Mixed with nested items
+
+- [x] Set up the project
+- [x] Install dependencies
+- [ ] Write tests
+ - [x] Unit tests
+ - [ ] Integration tests
+- [ ] Deploy
+
+## Checked and unchecked states
+
+- [x] This item is done
+- [ ] This item is not done
diff --git a/src/Elastic.ApiExplorer/Schema/SchemaHelpers.cs b/src/Elastic.ApiExplorer/Schema/SchemaHelpers.cs
index 3b403c11dd..b4c0652fd1 100644
--- a/src/Elastic.ApiExplorer/Schema/SchemaHelpers.cs
+++ b/src/Elastic.ApiExplorer/Schema/SchemaHelpers.cs
@@ -19,9 +19,8 @@ public static class SchemaHelpers
///
/// Types that are known to be value types (resolve to primitives like string).
///
- public static readonly HashSet KnownValueTypes =
- [
- with(StringComparer.OrdinalIgnoreCase),
+ public static readonly HashSet KnownValueTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
"Field", "Fields", "Id", "Ids", "IndexName", "Indices", "Name", "Names",
"Routing", "VersionNumber", "SequenceNumber", "PropertyName", "RelationName",
"TaskId", "ScrollId", "SuggestionName", "Duration", "DateMath", "Fuzziness",
@@ -29,28 +28,26 @@ public static class SchemaHelpers
"ByteSize", "Percentage", "Stringifiedboolean", "ExpandWildcards", "float", "Stringifiedinteger",
// Numeric value types
"uint", "ulong", "long", "int", "short", "ushort", "byte", "sbyte", "double", "decimal"
- ];
+ };
///
/// Types that have dedicated pages we can link to.
/// Only container types get their own pages - individual queries/aggregations are rendered inline.
///
- public static readonly HashSet LinkedTypes =
- [
- with(StringComparer.OrdinalIgnoreCase),
+ public static readonly HashSet LinkedTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
"QueryContainer", "AggregationContainer", "Aggregate"
- ];
+ };
///
/// Primitive/generic type names that are not named schema types.
/// These should not be considered for recursive type detection since they
/// represent generic types rather than specific schema references.
///
- public static readonly HashSet PrimitiveTypeNames =
- [
- with(StringComparer.OrdinalIgnoreCase),
+ public static readonly HashSet PrimitiveTypeNames = new(StringComparer.OrdinalIgnoreCase)
+ {
"boolean", "number", "string", "integer", "object", "null", "array"
- ];
+ };
///
/// Gets the URL for a container type's dedicated page.
diff --git a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
index f20055f83b..0051a9e75b 100644
--- a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
+++ b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
@@ -47,10 +47,10 @@ public record ConfigurationFile
public HashSet Products { get; private set; } = [];
- private readonly Dictionary _substitutions = [with(StringComparer.OrdinalIgnoreCase)];
+ private readonly Dictionary _substitutions = new(StringComparer.OrdinalIgnoreCase);
public IReadOnlyDictionary Substitutions => _substitutions;
- private readonly Dictionary _features = [with(StringComparer.OrdinalIgnoreCase)];
+ private readonly Dictionary _features = new(StringComparer.OrdinalIgnoreCase);
[field: AllowNull, MaybeNull]
public FeatureFlags Features => field ??= new FeatureFlags(_features);
@@ -259,7 +259,7 @@ public ConfigurationFile(DocumentationSetFile docSetFile, IDocumentationSetConte
Branding = ValidateBranding(docSetFile.Branding, context);
// Process features
- _features = [with(StringComparer.OrdinalIgnoreCase)];
+ _features = new(StringComparer.OrdinalIgnoreCase);
if (docSetFile.Features.PrimaryNav.HasValue)
_features["primary-nav"] = docSetFile.Features.PrimaryNav.Value;
if (docSetFile.Features.DisableGithubEditLink.HasValue)
diff --git a/src/Elastic.Documentation.Configuration/Builder/FeatureFlags.cs b/src/Elastic.Documentation.Configuration/Builder/FeatureFlags.cs
index 3edfad1fc8..73ca47910f 100644
--- a/src/Elastic.Documentation.Configuration/Builder/FeatureFlags.cs
+++ b/src/Elastic.Documentation.Configuration/Builder/FeatureFlags.cs
@@ -6,7 +6,7 @@ namespace Elastic.Documentation.Configuration.Builder;
public class FeatureFlags(Dictionary initFeatureFlags)
{
- private readonly Dictionary _featureFlags = [with(initFeatureFlags)];
+ private readonly Dictionary _featureFlags = new(initFeatureFlags);
public void Set(string key, bool value)
{
diff --git a/src/Elastic.Documentation.Configuration/Changelog/ChangelogConfigurationLoader.cs b/src/Elastic.Documentation.Configuration/Changelog/ChangelogConfigurationLoader.cs
index 38a73ee728..3c69632548 100644
--- a/src/Elastic.Documentation.Configuration/Changelog/ChangelogConfigurationLoader.cs
+++ b/src/Elastic.Documentation.Configuration/Changelog/ChangelogConfigurationLoader.cs
@@ -748,7 +748,7 @@ private static PivotConfiguration ConvertPivot(PivotConfigurationYaml yamlPivot)
Dictionary? byProduct = null;
if (yaml.Products is { Count: > 0 })
{
- byProduct = [with(StringComparer.OrdinalIgnoreCase)];
+ byProduct = new(StringComparer.OrdinalIgnoreCase);
foreach (var (productKey, productYaml) in yaml.Products)
{
var productIds = productKey.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
@@ -946,7 +946,7 @@ private static PivotConfiguration ConvertPivot(PivotConfigurationYaml yamlPivot)
Dictionary? byProduct = null;
if (yaml.Products is { Count: > 0 })
{
- byProduct = [with(StringComparer.OrdinalIgnoreCase)];
+ byProduct = new(StringComparer.OrdinalIgnoreCase);
foreach (var (productKey, productYaml) in yaml.Products)
{
var productIds = productKey.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
@@ -1011,7 +1011,7 @@ private static PivotConfiguration ConvertPivot(PivotConfigurationYaml yamlPivot)
Dictionary? byProduct = null;
if (yaml.Products is { Count: > 0 })
{
- byProduct = [with(StringComparer.OrdinalIgnoreCase)];
+ byProduct = new(StringComparer.OrdinalIgnoreCase);
foreach (var (productKey, productYaml) in yaml.Products)
{
var productIds = productKey.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
diff --git a/src/Elastic.Documentation.Site/Assets/markdown/list.css b/src/Elastic.Documentation.Site/Assets/markdown/list.css
index 18a942da9e..116cabd1c3 100644
--- a/src/Elastic.Documentation.Site/Assets/markdown/list.css
+++ b/src/Elastic.Documentation.Site/Assets/markdown/list.css
@@ -50,4 +50,23 @@
ol > li > ol > li > ol > li > ol > li > ol > li > ol {
list-style-type: lower-roman;
}
+
+ ul.contains-task-list {
+ list-style: none;
+ margin-left: 0.25em;
+
+ li.task-list-item {
+ display: flex;
+ align-items: baseline;
+ gap: 0.5em;
+ }
+
+ input[type='checkbox'] {
+ margin: 0;
+ flex-shrink: 0;
+ width: 1em;
+ height: 1em;
+ cursor: default;
+ }
+ }
}
diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs
index f6d035eebb..e08f4464dc 100644
--- a/src/Elastic.Markdown/IO/MarkdownFile.cs
+++ b/src/Elastic.Markdown/IO/MarkdownFile.cs
@@ -86,10 +86,10 @@ public virtual string NavigationTitle
//indexed by slug
- private readonly Dictionary _pageTableOfContent = [with(StringComparer.OrdinalIgnoreCase)];
+ private readonly Dictionary _pageTableOfContent = new(StringComparer.OrdinalIgnoreCase);
public IReadOnlyDictionary PageTableOfContent => _pageTableOfContent;
- private readonly HashSet _anchors = [with(StringComparer.OrdinalIgnoreCase)];
+ private readonly HashSet _anchors = new(StringComparer.OrdinalIgnoreCase);
public IReadOnlySet Anchors => _anchors;
public string FilePath { get; }
diff --git a/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs
index ae1d2f67c4..6a375a251e 100644
--- a/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs
+++ b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs
@@ -147,14 +147,14 @@ public class ChangelogBlock(DirectiveBlockParser parser, ParserContext context)
/// Links to these repositories will be hidden (commented out) in the rendered output.
/// Auto-detected from assembler configuration when available.
///
- public HashSet PrivateRepositories { get; private set; } = [with(StringComparer.OrdinalIgnoreCase)];
+ public HashSet PrivateRepositories { get; private set; } = new(StringComparer.OrdinalIgnoreCase);
///
/// Feature IDs that should be hidden when rendering changelog entries.
/// Combined from all loaded bundles' hide-features fields.
/// Entries with matching feature-id values will be excluded from the output.
///
- public HashSet HideFeatures { get; private set; } = [with(StringComparer.OrdinalIgnoreCase)];
+ public HashSet HideFeatures { get; private set; } = new(StringComparer.OrdinalIgnoreCase);
///
/// How to handle PR/issue links relative to private bundle repos (see :link-visibility: option).
diff --git a/src/Elastic.Markdown/Myst/MarkdownParser.cs b/src/Elastic.Markdown/Myst/MarkdownParser.cs
index 59bd5fb87c..309ae7a578 100644
--- a/src/Elastic.Markdown/Myst/MarkdownParser.cs
+++ b/src/Elastic.Markdown/Myst/MarkdownParser.cs
@@ -183,6 +183,7 @@ public static MarkdownPipeline Pipeline
.UseComments()
.UseYamlFrontMatter()
.UsePipeTables()
+ .UseTaskLists()
.UseDirectives()
.UseDefinitionLists()
.UseDefinitionTermAnchors()
diff --git a/tests/Elastic.Documentation.Build.Tests/MockEnvironmentVariables.cs b/tests/Elastic.Documentation.Build.Tests/MockEnvironmentVariables.cs
index 284b950c4a..3d1981e81f 100644
--- a/tests/Elastic.Documentation.Build.Tests/MockEnvironmentVariables.cs
+++ b/tests/Elastic.Documentation.Build.Tests/MockEnvironmentVariables.cs
@@ -14,7 +14,7 @@ namespace Elastic.Documentation.Build.Tests;
///
public class MockEnvironmentVariables : IEnvironmentVariables
{
- private readonly Dictionary _variables = [with(StringComparer.OrdinalIgnoreCase)];
+ private readonly Dictionary _variables = new(StringComparer.OrdinalIgnoreCase);
///
/// Sets an environment variable value for testing.
diff --git a/tests/Elastic.Markdown.Tests/TaskList/BasicTaskListTests.cs b/tests/Elastic.Markdown.Tests/TaskList/BasicTaskListTests.cs
new file mode 100644
index 0000000000..5d9ee124f8
--- /dev/null
+++ b/tests/Elastic.Markdown.Tests/TaskList/BasicTaskListTests.cs
@@ -0,0 +1,31 @@
+// 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 AwesomeAssertions;
+using Elastic.Markdown.Tests.Inline;
+
+namespace Elastic.Markdown.Tests.TaskList;
+
+public class BasicTaskListTests(ITestOutputHelper output)
+ : InlineTest(output, """
+- [ ] A pending task
+- [x] A completed task
+""")
+{
+ [Fact]
+ public void RendersTaskListContainer() =>
+ Html.Should().Contain("class=\"contains-task-list\"");
+
+ [Fact]
+ public void RendersTaskListItem() =>
+ Html.Should().Contain("class=\"task-list-item\"");
+
+ [Fact]
+ public void RendersUncheckedCheckbox() =>
+ Html.ShouldContainHtml("""""");
+
+ [Fact]
+ public void RendersCheckedCheckbox() =>
+ Html.ShouldContainHtml("""""");
+}