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(""""""); +}