diff --git a/.gitignore b/.gitignore index 937dec896..5e25d3253 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ tests/FSharp.Literate.Tests/output1/ .vscode/ .DS_Store tests/FSharp.Literate.Tests/output2/ +tests/FSharp.Literate.Tests/previous-next-output/ \ No newline at end of file diff --git a/docs/content.fsx b/docs/content.fsx index 37e5ff6f0..1275e3a4c 100644 --- a/docs/content.fsx +++ b/docs/content.fsx @@ -146,6 +146,8 @@ See [Styling](styling.html) for information about template parameters and stylin | `fsdocs-source-basename` | Name of original input source, excluding its extensions, relative to the `docs` root | | `fsdocs-tooltips` | Generated hidden div elements for tooltips | | `fsdocs-watch-script` | The websocket script used in watch mode to trigger hot reload | +| `fsdocs-previous-page-link` | A relative link to the previous page based on the frontmatter index data | +| `fsdocs-next-page-link` | A relative link to the next page based on the frontmatter index data | The following substitutions are extracted from your project files and may or may not be used by the default template: diff --git a/src/FSharp.Formatting.Common/Templating.fs b/src/FSharp.Formatting.Common/Templating.fs index 3e3d361b4..51745fa86 100644 --- a/src/FSharp.Formatting.Common/Templating.fs +++ b/src/FSharp.Formatting.Common/Templating.fs @@ -23,6 +23,48 @@ type ParamKey = /// A list of parameters for substituting in templates, indexed by parameter keys type Substitutions = (ParamKey * string) list +/// Meta data from files that contains front matter +/// Used to determine upfront which files have front matter so that previous and next substitutes can be discovered. +type FrontMatterFile = + { FileName: string + Category: string + CategoryIndex: int + Index: int } + + /// Parses the category, categoryindex and index from the frontmatter lines + static member ParseFromLines (fileName: string) (lines: string seq) = + let (|ValidIndex|_|) (value: string) = + match Int32.TryParse value with + | true, i -> Some i + | false, _ -> None + + let keyValues = + lines + // Skip opening lines + |> Seq.skipWhile (fun line -> + let line = line.Trim() + line = "(**" || line = "---") + |> Seq.takeWhile (fun line -> + // Allow empty lines in frontmatter + let isBlankLine = String.IsNullOrWhiteSpace line + isBlankLine || line.Contains(":")) + |> Seq.filter (String.IsNullOrWhiteSpace >> not) + |> Seq.map (fun line -> + let parts = line.Split(":") + parts.[0].ToLowerInvariant(), parts.[1]) + |> Map.ofSeq + + match + Map.tryFind "category" keyValues, Map.tryFind "categoryindex" keyValues, Map.tryFind "index" keyValues + with + | Some category, Some(ValidIndex categoryindex), Some(ValidIndex index) -> + Some + { FileName = fileName + Category = category.Trim() + CategoryIndex = categoryindex + Index = index } + | _ -> None + /// /// Defines the parameter keys known to FSharp.Formatting processing code /// @@ -134,6 +176,12 @@ module ParamKeys = /// A parameter key known to FSharp.Formatting, available in _menu-item_template.html let ``fsdocs-menu-item-id`` = ParamKey "fsdocs-menu-item-id" + /// A parameter key known to FSharp.Formatting, available when frontmatter is used correctly + let ``fsdocs-previous-page-link`` = ParamKey "fsdocs-previous-page-link" + + /// A parameter key known to FSharp.Formatting, available when frontmatter is used correctly + let ``fsdocs-next-page-link`` = ParamKey "fsdocs-next-page-link" + module internal SimpleTemplating = #if NETSTANDARD2_0 diff --git a/src/FSharp.Formatting.Literate/Contexts.fs b/src/FSharp.Formatting.Literate/Contexts.fs index 519a64d9d..07a8a395f 100644 --- a/src/FSharp.Formatting.Literate/Contexts.fs +++ b/src/FSharp.Formatting.Literate/Contexts.fs @@ -63,10 +63,10 @@ type internal LiterateDocModel = Category: string option /// The category index in the front matter (determines the order of categories) - CategoryIndex: string option + CategoryIndex: int option /// The index in the front matter (Determines the order of files within a category) - Index: string option + Index: int option /// The relative output path OutputPath: string diff --git a/src/FSharp.Formatting.Literate/Formatting.fs b/src/FSharp.Formatting.Literate/Formatting.fs index 95233b037..de0a70f3f 100644 --- a/src/FSharp.Formatting.Literate/Formatting.fs +++ b/src/FSharp.Formatting.Literate/Formatting.fs @@ -40,7 +40,7 @@ module internal Formatting = mdlinkResolver = mdlinkResolver ) | OutputKind.Html -> - let sb = new System.Text.StringBuilder() + let sb = System.Text.StringBuilder() use wr = new StringWriter(sb) HtmlFormatting.formatAsHtml @@ -105,11 +105,17 @@ module internal Formatting = | LiterateSource.Markdown text -> text | LiterateSource.Script snippets -> [ for Snippet(_name, lines) in snippets do - for (Line(line, _)) in lines do + for Line(line, _) in lines do yield line ] |> String.concat "\n" - let transformDocument (doc: LiterateDocument) (outputPath: string) ctx = + let transformDocument + // This array was sorted in BuildCommand.fs + (filesWithFrontMatter: FrontMatterFile array) + (doc: LiterateDocument) + (outputPath: string) + ctx + = let findInFrontMatter key = match doc.Paragraphs with @@ -126,10 +132,14 @@ module internal Formatting = None) | _ -> None - let category = findInFrontMatter "category" + let mkValidIndex (value: string) = + match System.Int32.TryParse value with + | true, i -> Some i + | false, _ -> None - let categoryIndex = findInFrontMatter "categoryindex" - let index = findInFrontMatter "index" + let category = findInFrontMatter "category" + let categoryIndex = findInFrontMatter "categoryindex" |> Option.bind mkValidIndex + let index = findInFrontMatter "index" |> Option.bind mkValidIndex let titleFromFrontMatter = findInFrontMatter "title" // If we want to include the source code of the script, then process @@ -167,10 +177,62 @@ module internal Formatting = // Replace all special elements with ordinary Html/Latex Markdown let doc = Transformations.replaceLiterateParagraphs ctx doc + // construct previous and next urls + let nextPreviousPageSubstitutions = + let getLinksFromCurrentPageIdx currentPageIdx = + match currentPageIdx with + | None -> [] + | Some currentPageIdx -> + let previousPage = + filesWithFrontMatter + |> Array.tryItem (currentPageIdx - 1) + |> Option.bind (fun { FileName = fileName } -> + ctx.MarkdownDirectLinkResolver fileName + |> Option.map (fun link -> ParamKeys.``fsdocs-previous-page-link``, link)) + |> Option.toList + + let nextPage = + filesWithFrontMatter + |> Array.tryItem (currentPageIdx + 1) + |> Option.bind (fun { FileName = fileName } -> + ctx.MarkdownDirectLinkResolver fileName + |> Option.map (fun link -> ParamKeys.``fsdocs-next-page-link``, link)) + |> Option.toList + + previousPage @ nextPage + + match index, categoryIndex with + | None, None + | None, Some _ -> + // Typical uses case here is the main index page. + // If there is no frontmatter there, we want to propose the first available page + filesWithFrontMatter + |> Array.tryHead + |> Option.bind (fun { FileName = fileName } -> + ctx.MarkdownDirectLinkResolver fileName + |> Option.map (fun link -> ParamKeys.``fsdocs-next-page-link``, link)) + |> Option.toList + + | Some currentPageIdx, None -> + let currentPageIdx = + filesWithFrontMatter + |> Array.tryFindIndex (fun { Index = idx } -> idx = currentPageIdx) + + getLinksFromCurrentPageIdx currentPageIdx + | Some currentPageIdx, Some currentCategoryIdx -> + let currentPageIdx = + filesWithFrontMatter + |> Array.tryFindIndex (fun { Index = idx; CategoryIndex = cIdx } -> + cIdx = currentCategoryIdx && idx = currentPageIdx) + + getLinksFromCurrentPageIdx currentPageIdx + let substitutions0 = - [ ParamKeys.``fsdocs-page-title``, pageTitle; ParamKeys.``fsdocs-page-source``, doc.SourceFile ] - @ ctx.Substitutions - @ sourceSubstitutions + [ yield ParamKeys.``fsdocs-page-title``, pageTitle + yield ParamKeys.``fsdocs-page-source``, doc.SourceFile + yield! ctx.Substitutions + yield! sourceSubstitutions + yield! nextPreviousPageSubstitutions ] let formattedDocument = format diff --git a/src/FSharp.Formatting.Literate/Literate.fs b/src/FSharp.Formatting.Literate/Literate.fs index 33d008a4c..380a5842b 100644 --- a/src/FSharp.Formatting.Literate/Literate.fs +++ b/src/FSharp.Formatting.Literate/Literate.fs @@ -5,7 +5,6 @@ open System.Collections.Generic open System.IO open System.Runtime.CompilerServices open FSharp.Formatting.Markdown -open FSharp.Formatting.CodeFormat open FSharp.Formatting.Templating /// @@ -260,7 +259,7 @@ type Literate private () = let doc = MarkdownDocument(doc.Paragraphs @ [ InlineHtmlBlock(doc.FormattedTips, None, None) ], doc.DefinedLinks) - let sb = new System.Text.StringBuilder() + let sb = System.Text.StringBuilder() use wr = new StringWriter(sb) HtmlFormatting.formatAsHtml @@ -424,7 +423,8 @@ type Literate private () = crefResolver, mdlinkResolver, parseOptions, - onError + onError, + filesWithFrontMatter: FrontMatterFile array ) = let parseOptions = @@ -457,7 +457,7 @@ type Literate private () = None let doc = downloadImagesForDoc imageSaver doc - let docModel = Formatting.transformDocument doc output ctx + let docModel = Formatting.transformDocument filesWithFrontMatter doc output ctx docModel /// Parse and transform an F# script file @@ -477,7 +477,8 @@ type Literate private () = rootInputFolder, crefResolver, mdlinkResolver, - onError + onError, + filesWithFrontMatter: FrontMatterFile array ) = let parseOptions = @@ -509,7 +510,7 @@ type Literate private () = None let doc = downloadImagesForDoc imageSaver doc - let docModel = Formatting.transformDocument doc output ctx + let docModel = Formatting.transformDocument filesWithFrontMatter doc output ctx docModel /// Convert a markdown file into HTML or another output kind @@ -529,7 +530,8 @@ type Literate private () = ?rootInputFolder, ?crefResolver, ?mdlinkResolver, - ?onError + ?onError, + ?filesWithFrontMatter ) = let outputKind = defaultArg outputKind OutputKind.Html @@ -537,6 +539,7 @@ type Literate private () = let crefResolver = defaultArg crefResolver (fun _ -> None) let mdlinkResolver = defaultArg mdlinkResolver (fun _ -> None) let substitutions = defaultArg substitutions [] + let filesWithFrontMatter = defaultArg filesWithFrontMatter Array.empty let res = Literate.ParseAndTransformMarkdownFile( @@ -554,7 +557,8 @@ type Literate private () = crefResolver = crefResolver, mdlinkResolver = mdlinkResolver, parseOptions = MarkdownParseOptions.AllowYamlFrontMatter, - onError = onError + onError = onError, + filesWithFrontMatter = filesWithFrontMatter ) SimpleTemplating.UseFileAsSimpleTemplate(res.Substitutions, template, output) @@ -582,7 +586,8 @@ type Literate private () = ?rootInputFolder, ?crefResolver, ?mdlinkResolver, - ?onError + ?onError, + ?filesWithFrontMatter ) = let outputKind = defaultArg outputKind OutputKind.Html @@ -590,6 +595,7 @@ type Literate private () = let crefResolver = defaultArg crefResolver (fun _ -> None) let mdlinkResolver = defaultArg mdlinkResolver (fun _ -> None) let substitutions = defaultArg substitutions [] + let filesWithFrontMatter = defaultArg filesWithFrontMatter Array.empty let res = Literate.ParseAndTransformScriptFile( @@ -607,7 +613,8 @@ type Literate private () = rootInputFolder = rootInputFolder, crefResolver = crefResolver, mdlinkResolver = mdlinkResolver, - onError = onError + onError = onError, + filesWithFrontMatter = filesWithFrontMatter ) SimpleTemplating.UseFileAsSimpleTemplate(res.Substitutions, template, output) diff --git a/src/FSharp.Formatting.Literate/ParseScript.fs b/src/FSharp.Formatting.Literate/ParseScript.fs index f022663bf..bbadafb46 100644 --- a/src/FSharp.Formatting.Literate/ParseScript.fs +++ b/src/FSharp.Formatting.Literate/ParseScript.fs @@ -1,6 +1,12 @@ namespace FSharp.Formatting.Literate open System.Collections.Generic +open System.Text +open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.Syntax +open FSharp.Compiler.SyntaxTrivia +open FSharp.Compiler.Text +open FSharp.Formatting.Templating open FSharp.Patterns open FSharp.Formatting.CodeFormat open FSharp.Formatting.Markdown @@ -389,3 +395,50 @@ type internal ParseScript(parseOptions, ctx: CompilerContext) = diagnostics = diagnostics, rootInputFolder = rootInputFolder ) + + /// Tries and parse the file to find and process the first block comment. + static member ParseFrontMatter(fileName: string) : FrontMatterFile option = + try + let sourceText = (System.IO.File.ReadAllText >> SourceText.ofString) fileName + let checker = FSharp.Formatting.Internal.CompilerServiceExtensions.FSharpAssemblyHelper.checker + + let parseResult = + checker.ParseFile( + fileName, + sourceText, + { FSharpParsingOptions.Default with + SourceFiles = [| fileName |] } + ) + |> Async.RunSynchronously + + match parseResult.ParseTree with + | ParsedInput.SigFile _ -> None + | ParsedInput.ImplFile(ParsedImplFileInput.ParsedImplFileInput(trivia = { CodeComments = codeComments })) -> + codeComments + |> List.tryPick (function + | CommentTrivia.BlockComment mBlockComment -> Some mBlockComment + | CommentTrivia.LineComment _ -> None) + |> Option.bind (fun mBlockComment -> + // Grab the comment text from the ISourceText + let commentText = + let startLine = mBlockComment.StartLine - 1 + let line = sourceText.GetLineString startLine + + if mBlockComment.StartLine = mBlockComment.EndLine then + let length = mBlockComment.EndColumn - mBlockComment.StartColumn + line.Substring(mBlockComment.StartColumn, length) + else + let firstLineContent = line.Substring(mBlockComment.StartColumn) + let sb = StringBuilder().AppendLine(firstLineContent) + + (sb, [ mBlockComment.StartLine .. mBlockComment.EndLine - 2 ]) + ||> List.fold (fun sb lineNumber -> sb.AppendLine(sourceText.GetLineString lineNumber)) + |> fun sb -> + let lastLine = sourceText.GetLineString(mBlockComment.EndLine - 1) + sb.Append(lastLine.Substring(0, mBlockComment.EndColumn)).ToString() + + let lines = commentText.Split '\n' + FrontMatterFile.ParseFromLines fileName lines) + with ex -> + printfn "Failed to find frontmatter in %s, %A" fileName ex + None diff --git a/src/fsdocs-tool/BuildCommand.fs b/src/fsdocs-tool/BuildCommand.fs index 44df7b7ff..8ceeb73c8 100644 --- a/src/fsdocs-tool/BuildCommand.fs +++ b/src/fsdocs-tool/BuildCommand.fs @@ -173,6 +173,7 @@ type internal DocContent outputFolderRelativeToRoot imageSaver mdlinkResolver + (filesWithFrontMatter: FrontMatterFile array) = [ let name = Path.GetFileName(inputFileFullPath) @@ -276,7 +277,8 @@ type internal DocContent rootInputFolder = rootInputFolder, crefResolver = crefResolver, mdlinkResolver = mdlinkResolver, - onError = Some onError + onError = Some onError, + filesWithFrontMatter = filesWithFrontMatter ) yield @@ -313,7 +315,8 @@ type internal DocContent crefResolver = crefResolver, mdlinkResolver = mdlinkResolver, parseOptions = MarkdownParseOptions.AllowYamlFrontMatter, - onError = Some onError + onError = Some onError, + filesWithFrontMatter = filesWithFrontMatter ) yield @@ -353,6 +356,7 @@ type internal DocContent (htmlTemplate, texTemplate, pynbTemplate, fsxTemplate, mdTemplate, isOtherLang, rootInputFolder, fullPathFileMap) (inputFolderAsGiven: string) outputFolderRelativeToRoot + (filesWithFrontMatter: FrontMatterFile array) = [ // Look for the presence of the _template.* files to activate the @@ -426,6 +430,7 @@ type internal DocContent fullPathFileMap, OutputKind.Html )) + filesWithFrontMatter yield! processFile @@ -442,6 +447,7 @@ type internal DocContent fullPathFileMap, OutputKind.Latex )) + filesWithFrontMatter yield! processFile @@ -458,6 +464,7 @@ type internal DocContent fullPathFileMap, OutputKind.Pynb )) + filesWithFrontMatter yield! processFile @@ -474,6 +481,7 @@ type internal DocContent fullPathFileMap, OutputKind.Fsx )) + filesWithFrontMatter yield! processFile @@ -490,6 +498,7 @@ type internal DocContent fullPathFileMap, OutputKind.Markdown )) + filesWithFrontMatter for subInputFolderFullPath in Directory.EnumerateDirectories(inputFolderAsGiven) do let subInputFolderName = Path.GetFileName(subInputFolderFullPath) @@ -511,7 +520,8 @@ type internal DocContent rootInputFolder, fullPathFileMap) (Path.Combine(inputFolderAsGiven, subInputFolderName)) - (Path.Combine(outputFolderRelativeToRoot, subInputFolderName)) ] + (Path.Combine(outputFolderRelativeToRoot, subInputFolderName)) + filesWithFrontMatter ] member _.Convert(rootInputFolderAsGiven, htmlTemplate, extraInputs) = @@ -523,12 +533,32 @@ type internal DocContent yield! prepFolder rootInputFolderAsGiven outputFolderRelativeToRoot ] |> Map.ofList + // In order to create {{next-page-url}} and {{previous-page-url}} + // We need to scan all *.fsx and *.md files for their frontmatter. + let filesWithFrontMatter = + fullPathFileMap + |> Map.keys + |> Seq.map fst + |> Seq.distinct + |> Seq.choose (fun fileName -> + let ext = Path.GetExtension fileName + + if ext = ".fsx" then + ParseScript.ParseFrontMatter(fileName) + elif ext = ".md" then + File.ReadLines fileName |> FrontMatterFile.ParseFromLines fileName + else + None) + |> Seq.sortBy (fun { Index = idx; CategoryIndex = cIdx } -> cIdx, idx) + |> Seq.toArray + [ for (rootInputFolderAsGiven, outputFolderRelativeToRoot) in inputDirectories do yield! processFolder (htmlTemplate, None, None, None, None, false, Some rootInputFolderAsGiven, fullPathFileMap) rootInputFolderAsGiven - outputFolderRelativeToRoot ] + outputFolderRelativeToRoot + filesWithFrontMatter ] member _.GetSearchIndexEntries(docModels: (string * bool * LiterateDocModel) list) = [| for (_inputFile, isOtherLang, model) in docModels do @@ -564,7 +594,7 @@ type internal DocContent Int32.MaxValue) | None -> Int32.MaxValue) - let orderList list = + let orderList (list: LiterateDocModel list) = list |> List.sortBy (fun model -> match model.Index with diff --git a/src/fsdocs-tool/packages.lock.json b/src/fsdocs-tool/packages.lock.json index 24ec0c9b8..ceea008e7 100644 --- a/src/fsdocs-tool/packages.lock.json +++ b/src/fsdocs-tool/packages.lock.json @@ -42,6 +42,32 @@ "FSharp.Core": "0.0.0" } }, + "Microsoft.Build": { + "type": "Transitive", + "resolved": "17.2.0", + "contentHash": "Rpf+7b8WHcgkrEQnGcV8FiAIl15ZC7Oit4t213fMoRdJb8SUYKdB71ZzWFaV5f9glN3y+R4cxRDY0dkwaIpwYw==", + "dependencies": { + "Microsoft.Build.Framework": "17.2.0", + "Microsoft.NET.StringTools": "1.0.0", + "Microsoft.Win32.Registry": "4.3.0", + "System.Collections.Immutable": "5.0.0", + "System.Configuration.ConfigurationManager": "4.7.0", + "System.Reflection.Metadata": "1.6.0", + "System.Security.Principal.Windows": "4.7.0", + "System.Text.Encoding.CodePages": "4.0.1", + "System.Text.Json": "6.0.0", + "System.Threading.Tasks.Dataflow": "6.0.0" + } + }, + "Microsoft.Build.Framework": { + "type": "Transitive", + "resolved": "17.2.0", + "contentHash": "5MtMF6vZeK8Nq6r9GctGpfiBa+r5+pTCnZJp8Bi6C74TysWl5ri6vmuLbsYhzvXqTPbnvDxCpKAI90z3m4m5mw==", + "dependencies": { + "Microsoft.Win32.Registry": "4.3.0", + "System.Security.Permissions": "4.7.0" + } + }, "Microsoft.NET.StringTools": { "type": "Transitive", "resolved": "1.0.0", @@ -419,34 +445,6 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, - "Microsoft.Build": { - "type": "CentralTransitive", - "requested": "(, )", - "resolved": "17.2.0", - "contentHash": "Rpf+7b8WHcgkrEQnGcV8FiAIl15ZC7Oit4t213fMoRdJb8SUYKdB71ZzWFaV5f9glN3y+R4cxRDY0dkwaIpwYw==", - "dependencies": { - "Microsoft.Build.Framework": "17.2.0", - "Microsoft.NET.StringTools": "1.0.0", - "Microsoft.Win32.Registry": "4.3.0", - "System.Collections.Immutable": "5.0.0", - "System.Configuration.ConfigurationManager": "4.7.0", - "System.Reflection.Metadata": "1.6.0", - "System.Security.Principal.Windows": "4.7.0", - "System.Text.Encoding.CodePages": "4.0.1", - "System.Text.Json": "6.0.0", - "System.Threading.Tasks.Dataflow": "6.0.0" - } - }, - "Microsoft.Build.Framework": { - "type": "CentralTransitive", - "requested": "(, )", - "resolved": "17.2.0", - "contentHash": "5MtMF6vZeK8Nq6r9GctGpfiBa+r5+pTCnZJp8Bi6C74TysWl5ri6vmuLbsYhzvXqTPbnvDxCpKAI90z3m4m5mw==", - "dependencies": { - "Microsoft.Win32.Registry": "4.3.0", - "System.Security.Permissions": "4.7.0" - } - }, "System.Memory": { "type": "CentralTransitive", "requested": "[4.5.5, )", diff --git a/tests/FSharp.Literate.Tests/DocContentTests.fs b/tests/FSharp.Literate.Tests/DocContentTests.fs index 9230c6231..94a2f90fc 100644 --- a/tests/FSharp.Literate.Tests/DocContentTests.fs +++ b/tests/FSharp.Literate.Tests/DocContentTests.fs @@ -186,3 +186,42 @@ let ``Can build doc content using relative input path`` () = let f2md2 = File.ReadAllText(rootOutputFolderAsGiven "folder2" "in-folder2.md") f1md1 |> shouldContainText """../folder2/in-folder2.md""" f2md2 |> shouldContainText """../folder1/in-folder1.md""" + +[] +let ``Parses frontmatter correctly `` () = + let rootOutputFolderAsGiven = __SOURCE_DIRECTORY__ "previous-next-output" + + let relativeInputFolderAsGiven = + Path.GetRelativePath(System.Environment.CurrentDirectory, __SOURCE_DIRECTORY__ "previous-next") + + 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://en.wikipedia.org", + crefResolver = (fun _ -> None), + onError = failwith + ) + + let docModels = content.Convert(relativeInputFolderAsGiven, None, []) + let globals = [] + + for _thing, action in docModels do + action globals + + let fellowshipHtml = rootOutputFolderAsGiven "fellowship.html" |> File.ReadAllText + let twoTowersHtml = rootOutputFolderAsGiven "two-tower.html" |> File.ReadAllText + let returnHtml = rootOutputFolderAsGiven "return.html" |> File.ReadAllText + + fellowshipHtml |> shouldContainText "Next" + twoTowersHtml |> shouldContainText "Previous" + twoTowersHtml |> shouldContainText "Next" + returnHtml |> shouldContainText "Previous" diff --git a/tests/FSharp.Literate.Tests/packages.lock.json b/tests/FSharp.Literate.Tests/packages.lock.json index e238f5064..805716cb4 100644 --- a/tests/FSharp.Literate.Tests/packages.lock.json +++ b/tests/FSharp.Literate.Tests/packages.lock.json @@ -59,6 +59,32 @@ "resolved": "4.5.0", "contentHash": "s8JpqTe9bI2f49Pfr3dFRfoVSuFQyraTj68c3XXjIS/MRGvvkLnrg6RLqnTjdShX+AdFUCCU/4Xex58AdUfs6A==" }, + "Microsoft.Build": { + "type": "Transitive", + "resolved": "17.2.0", + "contentHash": "Rpf+7b8WHcgkrEQnGcV8FiAIl15ZC7Oit4t213fMoRdJb8SUYKdB71ZzWFaV5f9glN3y+R4cxRDY0dkwaIpwYw==", + "dependencies": { + "Microsoft.Build.Framework": "17.2.0", + "Microsoft.NET.StringTools": "1.0.0", + "Microsoft.Win32.Registry": "4.3.0", + "System.Collections.Immutable": "5.0.0", + "System.Configuration.ConfigurationManager": "4.7.0", + "System.Reflection.Metadata": "1.6.0", + "System.Security.Principal.Windows": "4.7.0", + "System.Text.Encoding.CodePages": "4.0.1", + "System.Text.Json": "6.0.0", + "System.Threading.Tasks.Dataflow": "6.0.0" + } + }, + "Microsoft.Build.Framework": { + "type": "Transitive", + "resolved": "17.2.0", + "contentHash": "5MtMF6vZeK8Nq6r9GctGpfiBa+r5+pTCnZJp8Bi6C74TysWl5ri6vmuLbsYhzvXqTPbnvDxCpKAI90z3m4m5mw==", + "dependencies": { + "Microsoft.Win32.Registry": "4.3.0", + "System.Security.Permissions": "4.7.0" + } + }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "17.7.2", @@ -498,34 +524,6 @@ "resolved": "0.62.0", "contentHash": "gKFts9WmiK4x7FCBPMesLXMkVUXTs9tL3VRY4nHNhabIa2pi7+POeXTQJ/NjjE4+ksJ8ygaUkgkPEjZ8gaoE7g==" }, - "Microsoft.Build": { - "type": "CentralTransitive", - "requested": "(, )", - "resolved": "17.2.0", - "contentHash": "Rpf+7b8WHcgkrEQnGcV8FiAIl15ZC7Oit4t213fMoRdJb8SUYKdB71ZzWFaV5f9glN3y+R4cxRDY0dkwaIpwYw==", - "dependencies": { - "Microsoft.Build.Framework": "17.2.0", - "Microsoft.NET.StringTools": "1.0.0", - "Microsoft.Win32.Registry": "4.3.0", - "System.Collections.Immutable": "5.0.0", - "System.Configuration.ConfigurationManager": "4.7.0", - "System.Reflection.Metadata": "1.6.0", - "System.Security.Principal.Windows": "4.7.0", - "System.Text.Encoding.CodePages": "4.0.1", - "System.Text.Json": "6.0.0", - "System.Threading.Tasks.Dataflow": "6.0.0" - } - }, - "Microsoft.Build.Framework": { - "type": "CentralTransitive", - "requested": "(, )", - "resolved": "17.2.0", - "contentHash": "5MtMF6vZeK8Nq6r9GctGpfiBa+r5+pTCnZJp8Bi6C74TysWl5ri6vmuLbsYhzvXqTPbnvDxCpKAI90z3m4m5mw==", - "dependencies": { - "Microsoft.Win32.Registry": "4.3.0", - "System.Security.Permissions": "4.7.0" - } - }, "Newtonsoft.Json": { "type": "CentralTransitive", "requested": "[13.0.3, )", diff --git a/tests/FSharp.Literate.Tests/previous-next/fellowship.md b/tests/FSharp.Literate.Tests/previous-next/fellowship.md new file mode 100644 index 000000000..bfdcba378 --- /dev/null +++ b/tests/FSharp.Literate.Tests/previous-next/fellowship.md @@ -0,0 +1,9 @@ +--- +index: 1 +categoryindex: 1 +category: fiction +--- + +# The Fellowship of the ring + +[Next]({{fsdocs-next-page-link}}) \ No newline at end of file diff --git a/tests/FSharp.Literate.Tests/previous-next/return.md b/tests/FSharp.Literate.Tests/previous-next/return.md new file mode 100644 index 000000000..1a8838cd2 --- /dev/null +++ b/tests/FSharp.Literate.Tests/previous-next/return.md @@ -0,0 +1,9 @@ +--- +categoryindex: 1 +category: fiction +index: 3 +--- + +# The Return of the King + +[Previous]({{fsdocs-previous-page-link}}) \ No newline at end of file diff --git a/tests/FSharp.Literate.Tests/previous-next/two-tower.fsx b/tests/FSharp.Literate.Tests/previous-next/two-tower.fsx new file mode 100644 index 000000000..75fdef443 --- /dev/null +++ b/tests/FSharp.Literate.Tests/previous-next/two-tower.fsx @@ -0,0 +1,13 @@ +(** +--- +category: fiction +index: 2 + +categoryindex: 1 +--- + +# The Two Towers + +[Previous]({{fsdocs-previous-page-link}}) +[Next]({{fsdocs-next-page-link}}) +*) \ No newline at end of file