Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meta tags #888

Merged
merged 7 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<!-- Copy all project dependencies to bin folder -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<OtherFlags>$(OtherFlags) /warnon:1182 --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen</OtherFlags>
<OtherFlags>$(OtherFlags) /warnon:1182 --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen --strict-indentation+</OtherFlags>
</PropertyGroup>

<!-- NuGet Metadata -->
Expand Down
5 changes: 4 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 10 additions & 0 deletions docs/_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="{{fsdocs-authors}}" name="author">
<!-- Opengraph properties (https://ogp.me/) -->
<meta property="og:site_name" content="{{fsdocs-collection-name}}">
<meta property="og:title" content="{{fsdocs-page-title}}" />
<meta property="og:url" content="{{root}}{{fsdocs-source-basename}}.html">
<meta property="og:type" content="website" />
<!-- Twitter cards (https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary-card-with-large-image) -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="{{root}}">
<meta name="twitter:title" content="{{fsdocs-page-title}}">
{{fsdocs-meta-tags}}
<title>{{fsdocs-page-title}} | {{fsdocs-collection-name}}</title>
<link href="https://fonts.googleapis.com" rel="preconnect">
<link crossorigin href="https://fonts.gstatic.com" rel="preconnect">
Expand Down
9 changes: 8 additions & 1 deletion docs/content.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
```
Expand All @@ -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:
Expand All @@ -89,13 +91,17 @@ For F# scripts the frontmatter is in this form:
category: Examples
categoryindex: 2
index: 1
description: Some description
keywords: tag1, tag2, tag3
---
*)

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 `<meta name="description"` as part of the `{{fsdocs-meta-tags}}` substitution.
The `keywords` are also used in a meta tag as part of `{{fsdocs-meta-tags}}`. Separate them using a `,`.

## Link Translation for Inputs

Expand Down Expand Up @@ -151,6 +157,7 @@ See [Styling](styling.html) for information about template parameters and stylin
| `fsdocs-head-extra` | Additional html content loaded from the `_head.html` file if present in the `--input` folder |
| `fsdocs-body-extra` | Additional html content loaded from the `_body.html` file if present in the `--input` folder |
| `fsdocs-body-class` | A css class value to help distinguish between `content` and `api-docs` |
| `fsdocs-meta-tags` | A set of additional HTML meta tags, present when description and/or keywords are present in the frontmatter |

The following substitutions are extracted from your project files and may or may not be used by the default
template:
Expand Down
5 changes: 5 additions & 0 deletions src/FSharp.Formatting.Common/Templating.fs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ module ParamKeys =
/// This helps to differentiate styles between API docs and custom content.
let ``fsdocs-body-class`` = ParamKey "fsdocs-body-class"

/// A parameter key known to FSharp.Formatting, it is HTML composed from additional frontmatter information.
/// Such as tags and description
/// This can be empty when both properties are not provided for the current page.
let ``fsdocs-meta-tags`` = ParamKey "fsdocs-meta-tags"

module internal SimpleTemplating =

#if NETSTANDARD2_0
Expand Down
18 changes: 18 additions & 0 deletions src/FSharp.Formatting.Literate/Formatting.fs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ module internal Formatting =
let categoryIndex = findInFrontMatter "categoryindex" |> 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...
Expand Down Expand Up @@ -229,10 +231,26 @@ module internal Formatting =

getLinksFromCurrentPageIdx currentPageIdx

let meta =
let mkDescription description =
$"""<meta name="description" content="%s{description}">
<meta name="twitter:site" content="%s{description}">
<meta name="og:description" content="%s{description}">"""

let mkKeywords keywords =
$"""<meta name="keywords" content="%s{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 ]
Expand Down
49 changes: 48 additions & 1 deletion tests/FSharp.Literate.Tests/DocContentTests.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module FSharp.Literate.Tests.DocContent

open System.IO
open FSharp.Formatting.Templating
open fsdocs
open NUnit.Framework
open FsUnitTyped
Expand Down Expand Up @@ -255,6 +256,52 @@ let ``Parses frontmatter correctly `` () =
twoTowersHtml |> shouldContainText "<a href=\"return.html\">Next</a>"
returnHtml |> shouldContainText "<a href=\"two-tower.html\">Previous</a>"

[<Test>]
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("<meta name=\"description\"", meta)
StringAssert.Contains("Great description about StringAnalyzer!", meta)
StringAssert.Contains("fsharp, analyzers, tooling", meta)

(* Cannot get this test to evaluate the notebook
[<Test>]
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions tests/FSharp.Literate.Tests/FSharp.Literate.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<None Include="files/simple2.md" />
<None Include="files/template.html" />
<None Include="files/docpage.html" />
<None Include="files\seo-page.md" />
<Compile Include="..\Common\MarkdownUnit.fs">
<Link>Common\MarkdownUnit.fs</Link>
</Compile>
Expand Down
34 changes: 34 additions & 0 deletions tests/FSharp.Literate.Tests/files/seo-page.md
Original file line number Diff line number Diff line change
@@ -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)
```