diff --git a/Directory.Build.props b/Directory.Build.props index 013df25cb..b6f66d5fa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ true true - $(OtherFlags) /warnon:1182 --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen + $(OtherFlags) /warnon:1182 --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen --strict-indentation+ diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6bb45119c..5ae753cc9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,10 +1,13 @@ # Changelog -## Unreleased +## 20.0.0-alpha-015 - 2023-12-06 ### Fixed * Namespace description overflows content box. [#886](https://github.com/fsprojects/FSharp.Formatting/issues/886) +### Added +* SEO-optimization for new theme. Allow `description` and `keywords` in frontmatter. Introduce `{{fsdocs-meta-tags}}`. [#869](https://github.com/fsprojects/FSharp.Formatting/issues/869) + ## 20.0.0-alpha-014 - 2023-11-22 ### Added diff --git a/docs/_template.html b/docs/_template.html index 6348cc146..2f1910216 100644 --- a/docs/_template.html +++ b/docs/_template.html @@ -5,6 +5,16 @@ + + + + + + + + + + {{fsdocs-meta-tags}} {{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