{{fsdocs-page-title}} | {{fsdocs-collection-name}}
diff --git a/docs/content.fsx b/docs/content.fsx
index b686c6566..7ec8acd66 100644
--- a/docs/content.fsx
+++ b/docs/content.fsx
@@ -70,7 +70,7 @@ Any file or directory beginning with `.` is ignored.
## Front matter
-Each content file can have optional frontmatter. This determines the navigation bar title, categorization and ordering.
+Each content file can have optional frontmatter. This determines the navigation bar title, categorization ordering and meta tags.
For markdown, the format is:
```
@@ -79,6 +79,8 @@ title: Some Title
category: Some Category
categoryindex: 2
index: 3
+description: Some description
+keywords: tag1, tag2, tag3
---
```
For F# scripts the frontmatter is in this form:
@@ -89,6 +91,8 @@ For F# scripts the frontmatter is in this form:
category: Examples
categoryindex: 2
index: 1
+ description: Some description
+ keywords: tag1, tag2, tag3
---
*)
@@ -96,6 +100,8 @@ All entries are optional.
The `categoryindex` determines the ordering of categories.
The `index` determines the ordering of within each category.
The `title` is used in the navigation bar instead of any title inferred from the document.
+The `description` is used in ` Option.bind mkValidIndex
let index = findInFrontMatter "index" |> Option.bind mkValidIndex
let titleFromFrontMatter = findInFrontMatter "title"
+ let description = findInFrontMatter "description"
+ let tags = findInFrontMatter "keywords"
// If we want to include the source code of the script, then process
// the entire source and generate replacement {source} => ...some html...
@@ -229,10 +231,26 @@ module internal Formatting =
getLinksFromCurrentPageIdx currentPageIdx
+ let meta =
+ let mkDescription description =
+ $"""
+
+"""
+
+ let mkKeywords keywords =
+ $""""""
+
+ match description, tags with
+ | Some description, Some tags -> String.Concat(mkDescription description, "\n", mkKeywords tags)
+ | Some description, None -> mkDescription description
+ | None, Some keywords -> mkKeywords keywords
+ | None, None -> String.Empty
+
let substitutions0 =
[ yield ParamKeys.``fsdocs-page-title``, pageTitle
yield ParamKeys.``fsdocs-page-source``, doc.SourceFile
yield ParamKeys.``fsdocs-body-class``, "content"
+ yield ParamKeys.``fsdocs-meta-tags``, meta
yield! ctx.Substitutions
yield! sourceSubstitutions
yield! nextPreviousPageSubstitutions ]
diff --git a/tests/FSharp.Literate.Tests/DocContentTests.fs b/tests/FSharp.Literate.Tests/DocContentTests.fs
index 6cf6cf842..fac1e244e 100644
--- a/tests/FSharp.Literate.Tests/DocContentTests.fs
+++ b/tests/FSharp.Literate.Tests/DocContentTests.fs
@@ -1,6 +1,7 @@
module FSharp.Literate.Tests.DocContent
open System.IO
+open FSharp.Formatting.Templating
open fsdocs
open NUnit.Framework
open FsUnitTyped
@@ -255,6 +256,52 @@ let ``Parses frontmatter correctly `` () =
twoTowersHtml |> shouldContainText "Next"
returnHtml |> shouldContainText "Previous"
+[]
+let ``Parses description and keywords from frontmatter `` () =
+ let rootOutputFolderAsGiven = __SOURCE_DIRECTORY__ > "output1"
+
+ let relativeInputFolderAsGiven =
+ Path.GetRelativePath(System.Environment.CurrentDirectory, __SOURCE_DIRECTORY__ > "files")
+
+ if Directory.Exists(rootOutputFolderAsGiven) then
+ Directory.Delete(rootOutputFolderAsGiven, true)
+
+ let content =
+ DocContent(
+ rootOutputFolderAsGiven,
+ Map.empty,
+ lineNumbers = None,
+ evaluate = false,
+ substitutions = [],
+ saveImages = None,
+ watch = false,
+ root = "https://github.com",
+ crefResolver = (fun _ -> None),
+ onError = failwith
+ )
+
+ let docModels = content.Convert(relativeInputFolderAsGiven, None, [])
+
+ let seoPageDocModel =
+ docModels
+ |> List.pick (fun (docInfo, _substitutions) ->
+ match docInfo with
+ | Some(_, _, docModel) when docModel.Title = "StringAnalyzer" -> Some(docModel)
+ | _ -> None)
+
+ let globals = []
+
+ for _thing, action in docModels do
+ action globals
+
+ let meta =
+ seoPageDocModel.Substitutions
+ |> List.find (fst >> ((=) ParamKeys.``fsdocs-meta-tags``))
+ |> snd
+
+ StringAssert.Contains("]
@@ -283,7 +330,7 @@ let ``ipynb notebook evaluates`` () =
let globals = []
for (_thing, action) in docModels do
- action globals
+ action globals
let ipynbOut = rootOutputFolderAsGiven > "eval.html" |> File.ReadAllText
diff --git a/tests/FSharp.Literate.Tests/FSharp.Literate.Tests.fsproj b/tests/FSharp.Literate.Tests/FSharp.Literate.Tests.fsproj
index b5e3ca698..a931eff92 100644
--- a/tests/FSharp.Literate.Tests/FSharp.Literate.Tests.fsproj
+++ b/tests/FSharp.Literate.Tests/FSharp.Literate.Tests.fsproj
@@ -10,6 +10,7 @@
+
Common\MarkdownUnit.fs
diff --git a/tests/FSharp.Literate.Tests/files/seo-page.md b/tests/FSharp.Literate.Tests/files/seo-page.md
new file mode 100644
index 000000000..f281ec181
--- /dev/null
+++ b/tests/FSharp.Literate.Tests/files/seo-page.md
@@ -0,0 +1,34 @@
+---
+title: StringAnalyzer
+category: analyzers
+categoryindex: 1
+index: 3
+description: Great description about StringAnalyzer!
+keywords: fsharp, analyzers, tooling
+---
+
+# StringAnalyzer
+
+There are multiple analyzers for various string comparison functions:
+
+- String.EndsWith Analyzer
+- String.StartsWith Analyzer
+- String.IndexOf Analyzer
+
+## Problem
+
+The [recommendations for string usage](https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings#recommendations-for-string-usage) mention calling the overloads using `StringComparison` to increase performance.
+
+```fsharp
+"foo".EndsWith("p")
+```
+
+## Fix
+
+Signal your intention explicitly by calling an overload.
+
+```fsharp
+open System
+
+"foo".EndsWith("p", StringComparison.Ordinal)
+```
\ No newline at end of file