Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
* Fix crash (`failwith "tbd - IndirectImage"`) when `Markdown.ToMd` is called on a document containing reference-style images (`![alt][ref]`). The indirect image is now serialised as `![alt](url)` when the reference is resolved, or `![alt][ref]` when it is not. [#1094](https://github.com/fsprojects/FSharp.Formatting/pull/1094)
* Fix `Markdown.ToMd` serialising `*emphasis*` (italic) spans as `**...**` (bold) instead of `*...*`. [#1102](https://github.com/fsprojects/FSharp.Formatting/pull/1102)
* Fix `Markdown.ToMd` serialising ordered list items with 0-based numbering and no period (e.g. `0 first`) instead of 1-based with a period (e.g. `1. first`). [#1102](https://github.com/fsprojects/FSharp.Formatting/pull/1102)
* Fix `Markdown.ToMd` serialising a multi-paragraph blockquote as multiple separate blockquotes. The blank separator between paragraphs inside a `QuotedBlock` is now emitted as `>` (an empty blockquote line) instead of a plain blank line, so re-parsing the output yields a single `QuotedBlock` with all paragraphs intact. Also eliminates `> ` lines with trailing whitespace that the previous code produced.

### Changed
* Tooltips in generated documentation are now interactive: moving the mouse from a code token into the tooltip keeps it visible, so users can hover over, select, and copy text from the tooltip. The tooltip is dismissed when the mouse leaves it without returning to the originating token. [#949](https://github.com/fsprojects/FSharp.Formatting/issues/949)

## 22.0.0-alpha.2 - 2026-03-13

Expand Down
18 changes: 18 additions & 0 deletions docs/content/fsdocs-tips.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,28 @@ document.addEventListener('mouseout', function (evt) {
// Only hide when the mouse has left the trigger element entirely
if (target.contains(evt.relatedTarget)) return;
const name = target.dataset.fsdocsTip;
// Keep the tooltip visible when the mouse moves into it β€” this allows the user
// to hover over, select, and copy text from the tooltip.
const tipEl = document.getElementById(name);
if (tipEl && (evt.relatedTarget === tipEl || tipEl.contains(evt.relatedTarget))) return;
const unique = parseInt(target.dataset.fsdocsTipUnique, 10);
hideTip(name);
});

// Hide the tooltip when the mouse leaves it, unless it returns to the trigger element.
document.addEventListener('mouseout', function (evt) {
const tip = evt.target.closest('div.fsdocs-tip');
if (!tip) return;
// Still moving within the tooltip (between child elements) β€” keep it open.
if (tip.contains(evt.relatedTarget)) return;
// Mouse returned to the trigger that opened this tooltip β€” keep it open.
if (evt.relatedTarget) {
const trigger = evt.relatedTarget.closest('[data-fsdocs-tip]');
if (trigger && trigger.dataset.fsdocsTip === tip.id) return;
}
hideTip(tip.id);
});

function Clipboard_CopyTo(value) {
if (navigator.clipboard) {
navigator.clipboard.writeText(value);
Expand Down
13 changes: 11 additions & 2 deletions src/FSharp.Formatting.Markdown/MarkdownUtils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -238,13 +238,22 @@ module internal MarkdownUtils =
| YamlFrontmatter _ -> ()
| Span(body = body) -> yield formatSpans ctx body
| QuotedBlock(paragraphs = paragraphs) ->
for paragraph in paragraphs do
for (i, paragraph) in List.indexed paragraphs do
// Separate paragraphs within the same blockquote using an empty blockquote line.
// A plain blank line would close the blockquote, causing a round-trip failure.
if i > 0 then
yield ">"

let lines = formatParagraph ctx paragraph

// Drop the trailing blank line that formatParagraph normally appends;
// prefixing it with "> " would produce lines with trailing whitespace.
let lines = lines |> List.rev |> List.skipWhile System.String.IsNullOrEmpty |> List.rev

for line in lines do
yield "> " + line

yield ""
yield ""
| _ ->
printfn "// can't yet format %0A to markdown" paragraph
yield "" ]
Expand Down
26 changes: 26 additions & 0 deletions tests/FSharp.Markdown.Tests/Markdown.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,32 @@ let ``ToMd preserves a blockquote`` () =
result |> should contain "> "
result |> should contain "This is a quote."

[<Test>]
let ``ToMd preserves a multi-paragraph blockquote as a single blockquote`` () =
// A blockquote with two paragraphs must round-trip as one blockquote, not two.
// The separator between the two paragraphs must keep the ">" prefix so that
// re-parsing does not split it into separate QuotedBlock nodes.
let md = "> First paragraph.\n>\n> Second paragraph."
let result = toMd md
result |> should contain "> First paragraph."
result |> should contain "> Second paragraph."
// Re-parse and confirm we get exactly one QuotedBlock
let reparsed = Markdown.Parse(result)

match reparsed.Paragraphs with
| [ QuotedBlock _ ] -> () // single blockquote β€” correct
| other -> failwith $"Expected a single QuotedBlock but got: %A{other}"

[<Test>]
let ``ToMd blockquote does not produce trailing-whitespace lines`` () =
// "> " (greater-than + space) on its own is an empty blockquote line and has trailing whitespace.
// The serialiser should not emit such lines.
let md = "> A short quote."
let result = toMd md
let lines = result.Split('\n')

lines |> Array.filter (fun l -> l = "> ") |> Array.length |> should equal 0

[<Test>]
let ``ToMd preserves a horizontal rule`` () =
let md = "Before\n\n---\n\nAfter"
Expand Down
Loading