From deeb33a28edaa88e4db7439b94046e0a031a3176 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 22 Jan 2025 10:20:01 +0100 Subject: [PATCH 01/13] Fix inline code in left navigation and h1 rendering --- docs/contribute/locally.md | 4 ++-- src/Elastic.Markdown/Helpers/TitleSanitizer.cs | 8 ++++++++ src/Elastic.Markdown/IO/MarkdownFile.cs | 7 +++++-- src/Elastic.Markdown/Slices/Index.cshtml | 5 +++-- 4 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 src/Elastic.Markdown/Helpers/TitleSanitizer.cs diff --git a/docs/contribute/locally.md b/docs/contribute/locally.md index 7818afbe7..7c723ed12 100644 --- a/docs/contribute/locally.md +++ b/docs/contribute/locally.md @@ -1,4 +1,4 @@ -# Contribute locally +# Contribute locally `This is a code block` Follow these steps to contribute to Elastic docs. * [Step 1: Install `docs-builder`](#step-one) @@ -116,4 +116,4 @@ Open a PR. Good luck. ## Step 5: View on elastic.co/docs -soon... \ No newline at end of file +soon... diff --git a/src/Elastic.Markdown/Helpers/TitleSanitizer.cs b/src/Elastic.Markdown/Helpers/TitleSanitizer.cs new file mode 100644 index 000000000..d7d8968cf --- /dev/null +++ b/src/Elastic.Markdown/Helpers/TitleSanitizer.cs @@ -0,0 +1,8 @@ +namespace Elastic.Markdown.Helpers; + +public static class TitleSanitizer +{ + // Removes markdown formatting from the title and returns only the text + // Currently, only support 'bold' and 'code' formatting + public static string Sanitize(string title) => title.Replace("`", "").Replace("*", ""); +} diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index e5ff57189..99154578a 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -46,7 +46,10 @@ public DocumentationGroup? Parent public string? Title { get; private set; } public string? NavigationTitle { - get => !string.IsNullOrEmpty(_navigationTitle) ? _navigationTitle : Title; + get { + var title = !string.IsNullOrEmpty(_navigationTitle) ? _navigationTitle : Title; + return string.IsNullOrEmpty(title) ? null : TitleSanitizer.Sanitize(title); + } private set => _navigationTitle = value; } @@ -159,7 +162,7 @@ private void ReadDocumentInstructions(MarkdownDocument document) .Select(h => (h.GetData("header") as string, h.GetData("anchor") as string)) .Select(h => new PageTocItem { - Heading = h.Item1!.Replace("`", "").Replace("*", ""), + Heading = TitleSanitizer.Sanitize(h.Item1!), Slug = _slugHelper.GenerateSlug(h.Item2 ?? h.Item1) }) .ToList(); diff --git a/src/Elastic.Markdown/Slices/Index.cshtml b/src/Elastic.Markdown/Slices/Index.cshtml index 0a03dbc52..09c41e01b 100644 --- a/src/Elastic.Markdown/Slices/Index.cshtml +++ b/src/Elastic.Markdown/Slices/Index.cshtml @@ -1,9 +1,10 @@ +@using Markdig @inherits RazorSliceHttpResult @implements IUsesLayout @functions { public LayoutViewModel LayoutModel => new() { - Title = $"Elastic Documentation: {Model.Title}", + Title = $"Elastic Documentation: {Model.Title ?? "Untitled"}", PageTocItems = Model.PageTocItems, Tree = Model.Tree, CurrentDocument = Model.CurrentDocument, @@ -16,7 +17,7 @@ }; }
-

@(Model.Title)

+ @(new HtmlString(Markdown.ToHtml("# " + Model.Title))) @if (Model.Applies is not null) { await RenderPartialAsync(Applies.Create(Model.Applies)); From 921f8ea54eb4997c10e07f4baec6e15a9e5ff461 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 22 Jan 2025 11:09:03 +0100 Subject: [PATCH 02/13] Format and license --- src/Elastic.Markdown/Helpers/TitleSanitizer.cs | 13 ++++++++----- src/Elastic.Markdown/IO/MarkdownFile.cs | 7 ++++--- src/Elastic.Markdown/Slices/Index.cshtml | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Elastic.Markdown/Helpers/TitleSanitizer.cs b/src/Elastic.Markdown/Helpers/TitleSanitizer.cs index d7d8968cf..121552289 100644 --- a/src/Elastic.Markdown/Helpers/TitleSanitizer.cs +++ b/src/Elastic.Markdown/Helpers/TitleSanitizer.cs @@ -1,8 +1,11 @@ -namespace Elastic.Markdown.Helpers; +// 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 -public static class TitleSanitizer +namespace Elastic.Markdown.Helpers; +public static class TitleNormalizer { - // Removes markdown formatting from the title and returns only the text - // Currently, only support 'bold' and 'code' formatting - public static string Sanitize(string title) => title.Replace("`", "").Replace("*", ""); + // Removes markdown formatting from the title and returns only the text + // Currently, only support 'bold' and 'code' formatting + public static string Normalize(string title) => title.Replace("`", "").Replace("*", ""); } diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index 99154578a..afaf5a5c9 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -46,9 +46,10 @@ public DocumentationGroup? Parent public string? Title { get; private set; } public string? NavigationTitle { - get { + get + { var title = !string.IsNullOrEmpty(_navigationTitle) ? _navigationTitle : Title; - return string.IsNullOrEmpty(title) ? null : TitleSanitizer.Sanitize(title); + return string.IsNullOrEmpty(title) ? null : TitleNormalizer.Normalize(title); } private set => _navigationTitle = value; } @@ -162,7 +163,7 @@ private void ReadDocumentInstructions(MarkdownDocument document) .Select(h => (h.GetData("header") as string, h.GetData("anchor") as string)) .Select(h => new PageTocItem { - Heading = TitleSanitizer.Sanitize(h.Item1!), + Heading = TitleNormalizer.Normalize(h.Item1!), Slug = _slugHelper.GenerateSlug(h.Item2 ?? h.Item1) }) .ToList(); diff --git a/src/Elastic.Markdown/Slices/Index.cshtml b/src/Elastic.Markdown/Slices/Index.cshtml index 09c41e01b..f1a59c9ef 100644 --- a/src/Elastic.Markdown/Slices/Index.cshtml +++ b/src/Elastic.Markdown/Slices/Index.cshtml @@ -17,7 +17,7 @@ }; }
- @(new HtmlString(Markdown.ToHtml("# " + Model.Title))) +

@(new HtmlString(Markdown.ToHtml("#" + Model.Title)))

@if (Model.Applies is not null) { await RenderPartialAsync(Applies.Create(Model.Applies)); From 19327bf2db39580700290da2cc0eb59059598a6c Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 22 Jan 2025 11:18:34 +0100 Subject: [PATCH 03/13] Revert locally.md --- docs/contribute/locally.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contribute/locally.md b/docs/contribute/locally.md index 7c723ed12..7818afbe7 100644 --- a/docs/contribute/locally.md +++ b/docs/contribute/locally.md @@ -1,4 +1,4 @@ -# Contribute locally `This is a code block` +# Contribute locally Follow these steps to contribute to Elastic docs. * [Step 1: Install `docs-builder`](#step-one) @@ -116,4 +116,4 @@ Open a PR. Good luck. ## Step 5: View on elastic.co/docs -soon... +soon... \ No newline at end of file From 97f4c949bfa0138a0358ba9cd1da4a05e7bd42a0 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 22 Jan 2025 11:23:44 +0100 Subject: [PATCH 04/13] fix --- src/Elastic.Markdown/Slices/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/Slices/Index.cshtml b/src/Elastic.Markdown/Slices/Index.cshtml index f1a59c9ef..09c41e01b 100644 --- a/src/Elastic.Markdown/Slices/Index.cshtml +++ b/src/Elastic.Markdown/Slices/Index.cshtml @@ -17,7 +17,7 @@ }; }
-

@(new HtmlString(Markdown.ToHtml("#" + Model.Title)))

+ @(new HtmlString(Markdown.ToHtml("# " + Model.Title))) @if (Model.Applies is not null) { await RenderPartialAsync(Applies.Create(Model.Applies)); From 5e076576116ce53d478339b42089f16f4ed589d6 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 22 Jan 2025 11:25:34 +0100 Subject: [PATCH 05/13] Add comment --- src/Elastic.Markdown/Slices/Index.cshtml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Elastic.Markdown/Slices/Index.cshtml b/src/Elastic.Markdown/Slices/Index.cshtml index 09c41e01b..e26465c0f 100644 --- a/src/Elastic.Markdown/Slices/Index.cshtml +++ b/src/Elastic.Markdown/Slices/Index.cshtml @@ -17,6 +17,7 @@ }; }
+ @* This way it's correctly rendered as

text

instead of

text

*@ @(new HtmlString(Markdown.ToHtml("# " + Model.Title))) @if (Model.Applies is not null) { From 15ee32dd31c6d7a1532f3d1ea195c950ed76810b Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 23 Jan 2025 10:04:08 +0100 Subject: [PATCH 06/13] Refactor and use Markdig to strip markdown --- .../Helpers/{TitleSanitizer.cs => Markdown.cs} | 11 ++++++++--- src/Elastic.Markdown/IO/MarkdownFile.cs | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) rename src/Elastic.Markdown/Helpers/{TitleSanitizer.cs => Markdown.cs} (60%) diff --git a/src/Elastic.Markdown/Helpers/TitleSanitizer.cs b/src/Elastic.Markdown/Helpers/Markdown.cs similarity index 60% rename from src/Elastic.Markdown/Helpers/TitleSanitizer.cs rename to src/Elastic.Markdown/Helpers/Markdown.cs index 121552289..78d17985e 100644 --- a/src/Elastic.Markdown/Helpers/TitleSanitizer.cs +++ b/src/Elastic.Markdown/Helpers/Markdown.cs @@ -3,9 +3,14 @@ // See the LICENSE file in the project root for more information namespace Elastic.Markdown.Helpers; -public static class TitleNormalizer + +public static class Markdown { // Removes markdown formatting from the title and returns only the text - // Currently, only support 'bold' and 'code' formatting - public static string Normalize(string title) => title.Replace("`", "").Replace("*", ""); + public static string StripMarkdown(string markdown) + { + using var writer = new StringWriter(); + Markdig.Markdown.ToPlainText(markdown, writer); + return writer.ToString(); + } } diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index afaf5a5c9..c40a6c04d 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -49,7 +49,7 @@ public string? NavigationTitle get { var title = !string.IsNullOrEmpty(_navigationTitle) ? _navigationTitle : Title; - return string.IsNullOrEmpty(title) ? null : TitleNormalizer.Normalize(title); + return string.IsNullOrEmpty(title) ? null : Helpers.Markdown.StripMarkdown(title); } private set => _navigationTitle = value; } @@ -163,7 +163,7 @@ private void ReadDocumentInstructions(MarkdownDocument document) .Select(h => (h.GetData("header") as string, h.GetData("anchor") as string)) .Select(h => new PageTocItem { - Heading = TitleNormalizer.Normalize(h.Item1!), + Heading = Helpers.Markdown.StripMarkdown(h.Item1!), Slug = _slugHelper.GenerateSlug(h.Item2 ?? h.Item1) }) .ToList(); From 101b0be196c987da50fdf02b656388650d5227d1 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 23 Jan 2025 10:10:40 +0100 Subject: [PATCH 07/13] Trim result because it sometimes has a newline --- src/Elastic.Markdown/Helpers/Markdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/Helpers/Markdown.cs b/src/Elastic.Markdown/Helpers/Markdown.cs index 78d17985e..893ab60b9 100644 --- a/src/Elastic.Markdown/Helpers/Markdown.cs +++ b/src/Elastic.Markdown/Helpers/Markdown.cs @@ -11,6 +11,6 @@ public static string StripMarkdown(string markdown) { using var writer = new StringWriter(); Markdig.Markdown.ToPlainText(markdown, writer); - return writer.ToString(); + return writer.ToString().TrimEnd('\n'); } } From 226d8347f3852c7b48a90201d704a584d5ec5d05 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 23 Jan 2025 10:11:09 +0100 Subject: [PATCH 08/13] Remove obvious comment --- src/Elastic.Markdown/Helpers/Markdown.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Elastic.Markdown/Helpers/Markdown.cs b/src/Elastic.Markdown/Helpers/Markdown.cs index 893ab60b9..f6d551814 100644 --- a/src/Elastic.Markdown/Helpers/Markdown.cs +++ b/src/Elastic.Markdown/Helpers/Markdown.cs @@ -6,7 +6,6 @@ namespace Elastic.Markdown.Helpers; public static class Markdown { - // Removes markdown formatting from the title and returns only the text public static string StripMarkdown(string markdown) { using var writer = new StringWriter(); From 3c46f20efc265ab4fd48d4b61fc64517d601ae16 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 23 Jan 2025 10:40:03 +0100 Subject: [PATCH 09/13] Unnecessary check --- src/Elastic.Markdown/Slices/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/Slices/Index.cshtml b/src/Elastic.Markdown/Slices/Index.cshtml index e26465c0f..6c3ef6e6f 100644 --- a/src/Elastic.Markdown/Slices/Index.cshtml +++ b/src/Elastic.Markdown/Slices/Index.cshtml @@ -4,7 +4,7 @@ @functions { public LayoutViewModel LayoutModel => new() { - Title = $"Elastic Documentation: {Model.Title ?? "Untitled"}", + Title = $"Elastic Documentation: {Model.Title}", PageTocItems = Model.PageTocItems, Tree = Model.Tree, CurrentDocument = Model.CurrentDocument, From 2b4e21d20d4adf72c6cc700cc168d74fe45c31e8 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 23 Jan 2025 12:39:36 +0100 Subject: [PATCH 10/13] Set once --- src/Elastic.Markdown/IO/MarkdownFile.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index c40a6c04d..310817e9e 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -46,12 +46,8 @@ public DocumentationGroup? Parent public string? Title { get; private set; } public string? NavigationTitle { - get - { - var title = !string.IsNullOrEmpty(_navigationTitle) ? _navigationTitle : Title; - return string.IsNullOrEmpty(title) ? null : Helpers.Markdown.StripMarkdown(title); - } - private set => _navigationTitle = value; + get => !string.IsNullOrEmpty(_navigationTitle) ? _navigationTitle : Title; + private set => _navigationTitle = value != null ? Helpers.Markdown.StripMarkdown(value) : null; } //indexed by slug From 4ddc4bb0829faa2b1e5f20bd23d646c69a7aa81e Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 23 Jan 2025 16:10:36 +0100 Subject: [PATCH 11/13] Use extension method --- src/Elastic.Markdown/Helpers/Markdown.cs | 4 ++-- src/Elastic.Markdown/IO/MarkdownFile.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Elastic.Markdown/Helpers/Markdown.cs b/src/Elastic.Markdown/Helpers/Markdown.cs index f6d551814..9ab56ea8e 100644 --- a/src/Elastic.Markdown/Helpers/Markdown.cs +++ b/src/Elastic.Markdown/Helpers/Markdown.cs @@ -4,9 +4,9 @@ namespace Elastic.Markdown.Helpers; -public static class Markdown +public static class MarkdownStringExtensions { - public static string StripMarkdown(string markdown) + public static string StripMarkdown(this string markdown) { using var writer = new StringWriter(); Markdig.Markdown.ToPlainText(markdown, writer); diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index 310817e9e..961b91054 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -47,7 +47,7 @@ public DocumentationGroup? Parent public string? NavigationTitle { get => !string.IsNullOrEmpty(_navigationTitle) ? _navigationTitle : Title; - private set => _navigationTitle = value != null ? Helpers.Markdown.StripMarkdown(value) : null; + private set => _navigationTitle = value?.StripMarkdown(); } //indexed by slug @@ -159,7 +159,7 @@ private void ReadDocumentInstructions(MarkdownDocument document) .Select(h => (h.GetData("header") as string, h.GetData("anchor") as string)) .Select(h => new PageTocItem { - Heading = Helpers.Markdown.StripMarkdown(h.Item1!), + Heading = h.Item1!.StripMarkdown(), Slug = _slugHelper.GenerateSlug(h.Item2 ?? h.Item1) }) .ToList(); From e00d555e7d3d962ca93f06798074c24b261c20fe Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 23 Jan 2025 16:17:13 +0100 Subject: [PATCH 12/13] Add TitleRaw property --- docs/testing/req.md | 3 ++- src/Elastic.Markdown/IO/MarkdownFile.cs | 13 ++++++++++++- src/Elastic.Markdown/Slices/HtmlWriter.cs | 1 + src/Elastic.Markdown/Slices/Index.cshtml | 2 +- src/Elastic.Markdown/Slices/_ViewModels.cs | 2 ++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/testing/req.md b/docs/testing/req.md index 4617b62d4..7c870fa95 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -1,4 +1,5 @@ -# Requirements +# Requirements [link](https://www.elastic.co) `code` +## Requirements [link](https://www.elastic.co) `code` To follow this tutorial you will need to install the following components: diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index 961b91054..f697caaa7 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -43,7 +43,17 @@ public DocumentationGroup? Parent public string? UrlPathPrefix { get; } private MarkdownParser MarkdownParser { get; } public YamlFrontMatter? YamlFrontMatter { get; private set; } - public string? Title { get; private set; } + public string? TitleRaw { get; private set; } + + public string? Title + { + get => _title; + private set + { + _title = value?.StripMarkdown(); + TitleRaw = value; + } + } public string? NavigationTitle { get => !string.IsNullOrEmpty(_navigationTitle) ? _navigationTitle : Title; @@ -65,6 +75,7 @@ public string? NavigationTitle private bool _instructionsParsed; private DocumentationGroup? _parent; + private string? _title; public MarkdownFile[] YieldParents() { diff --git a/src/Elastic.Markdown/Slices/HtmlWriter.cs b/src/Elastic.Markdown/Slices/HtmlWriter.cs index aac30edf6..23ef8aecc 100644 --- a/src/Elastic.Markdown/Slices/HtmlWriter.cs +++ b/src/Elastic.Markdown/Slices/HtmlWriter.cs @@ -56,6 +56,7 @@ public async Task RenderLayout(MarkdownFile markdown, Cancel ctx = defau var slice = Index.Create(new IndexViewModel { Title = markdown.Title ?? "[TITLE NOT SET]", + TitleRaw = markdown.TitleRaw ?? "[TITLE NOT SET]", MarkdownHtml = html, PageTocItems = markdown.TableOfContents.Values.ToList(), Tree = DocumentationSet.Tree, diff --git a/src/Elastic.Markdown/Slices/Index.cshtml b/src/Elastic.Markdown/Slices/Index.cshtml index 6c3ef6e6f..3abb070d1 100644 --- a/src/Elastic.Markdown/Slices/Index.cshtml +++ b/src/Elastic.Markdown/Slices/Index.cshtml @@ -18,7 +18,7 @@ }
@* This way it's correctly rendered as

text

instead of

text

*@ - @(new HtmlString(Markdown.ToHtml("# " + Model.Title))) + @(new HtmlString(Markdown.ToHtml("# " + Model.TitleRaw))) @if (Model.Applies is not null) { await RenderPartialAsync(Applies.Create(Model.Applies)); diff --git a/src/Elastic.Markdown/Slices/_ViewModels.cs b/src/Elastic.Markdown/Slices/_ViewModels.cs index 3e89a70ff..987f5f6da 100644 --- a/src/Elastic.Markdown/Slices/_ViewModels.cs +++ b/src/Elastic.Markdown/Slices/_ViewModels.cs @@ -10,6 +10,7 @@ namespace Elastic.Markdown.Slices; public class IndexViewModel { public required string Title { get; init; } + public required string TitleRaw { get; init; } public required string MarkdownHtml { get; init; } public required DocumentationGroup Tree { get; init; } public required IReadOnlyCollection PageTocItems { get; init; } @@ -26,6 +27,7 @@ public class IndexViewModel public class LayoutViewModel { public string Title { get; set; } = "Elastic Documentation"; + public string RawTitle { get; set; } = "Elastic Documentation"; public required IReadOnlyCollection PageTocItems { get; init; } public required DocumentationGroup Tree { get; init; } public required MarkdownFile CurrentDocument { get; init; } From 9131b38b14d3324b2830ca4b7daa61916ddb3d8e Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 23 Jan 2025 16:19:27 +0100 Subject: [PATCH 13/13] Revert req.md --- docs/testing/req.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/testing/req.md b/docs/testing/req.md index 7c870fa95..4617b62d4 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -1,5 +1,4 @@ -# Requirements [link](https://www.elastic.co) `code` -## Requirements [link](https://www.elastic.co) `code` +# Requirements To follow this tutorial you will need to install the following components: