Skip to content

Continue the j / k hotkeys through the API reference tables - #1328

Merged
nojaf merged 6 commits into
fsprojects:mainfrom
nojaf:improve-api-doc-pages
Sep 15, 2026
Merged

nojaf merged 6 commits into
fsprojects:mainfrom
nojaf:improve-api-doc-pages

Conversation

@nojaf

@nojaf nojaf commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

The API reference index and the namespace pages carry their content in a
table rather than under headings, so j / k had almost nothing to stop at
there: two headings on the index, one on a namespace page, and then the
keys went dead over the list the reader actually came for.

The generator now marks the links of those two tables with
data-fsdocs-nav, and the hotkeys treat every heading plus every marked
link as a stop, in document order. The member tables of a module or a
type page are left unmarked: those pages have headings of their own, and
stopping at every member would bury them.

A heading still scrolls to the top of the content with its section below
it; a marked link only scrolls when it is off screen, so holding j walks
down a long table instead of paging it one row per screen. A marked link
has no anchor of its own, so walking a table leaves the URL alone.

data-fsdocs-nav is not specific to the API reference, so a link in
hand-written content can opt in the same way.

Before:
image

After:
image

Ever API docs page now also has a right "On this page" section (similar to how content has this):

image

The API reference index and the namespace pages carry their content in a
table rather than under headings, so j / k had almost nothing to stop at
there: two headings on the index, one on a namespace page, and then the
keys went dead over the list the reader actually came for.

The generator now marks the links of those two tables with
data-fsdocs-nav, and the hotkeys treat every heading plus every marked
link as a stop, in document order. The member tables of a module or a
type page are left unmarked: those pages have headings of their own, and
stopping at every member would bury them.

A heading still scrolls to the top of the content with its section below
it; a marked link only scrolls when it is off screen, so holding j walks
down a long table instead of paging it one row per screen. A marked link
has no anchor of its own, so walking a table leaves the URL alone.

data-fsdocs-nav is not specific to the API reference, so a link in
hand-written content can opt in the same way.
The API reference was the one part of the site with neither menu working.
The pages carry their content in tables rather than under headings, so the
"On this page" menu was hidden there, and the menu on the left showed a
single "All Namespaces" link with nothing to navigate.

Both now work. A namespace page lists its types and modules in the page
menu, each entry linking to that type's own page rather than to the row on
the page the reader is already on. A type or module page lists its
sections, which needed anchors of their own: "Static members", "Nested
modules" and "Inherited members" had none and could not be linked to at
all. The index of all namespaces gets no menu, since the page is already
that list, and keeps the wide two column layout. The inline "Table of
contents" and "Contents" blocks are gone, the menu replaces them. Marking
the current section reuses the scroll driven animations of the content
pages, over the section headings only, because each tracked element costs
three generated CSS rules and a module can have hundreds of members.

The menu on the left was a bug rather than a design. Every API page
computed the namespace list and then had it silently replaced, because the
global substitutions define fsdocs-list-of-namespaces as well, with the
collapsed link meant for the content pages, and the last value for a key
wins. Its entries now drop the prefix that all the namespaces of a
collection share, which otherwise left a column of identically truncated
names, with the full name on the title of the entry. The header links to
the index, and the entity list that expanded under the current namespace
is gone now that the page menu covers it.

Grouping the model by category is memoised in both renderers. It is the
same answer for the lifetime of a renderer, and it was being computed once
per page of the whole reference.

Signature files for GenerateHtml and GenerateMarkdown come along, moving
the documentation of the internals out of the implementation.
@nojaf
nojaf marked this pull request as ready for review September 15, 2026 17:42
The namespace list rendered from `_menu_template.html` marked nothing as
current: every item was built with `IsActive = false`, and the category
itself was passed `false` as well. A template that folds its sections away
therefore rendered the one section the reader was actually inside closed,
and no entry stood out as the page being read.

The list built without menu templates has always marked both, so this was
the templated path falling behind rather than a deliberate difference. It
stayed invisible while the API pages showed the collapsed "All Namespaces"
link instead of the namespace list.

An entry is now active when it is the namespace of the page being shown,
and the category is active whenever the menu is rendered for an API page,
which is exactly when the reader is inside it. The content pages render
the same list while the reader is elsewhere, so their section is left as
it was.
A menu template could only use the handful of keys it was handed, so it
could link where fsdocs told it to and nowhere else. Writing {{root}} in
one reached the browser with the braces intact, because the substitutions
are applied in a single pass: a menu is a value inserted into the page,
and values are never rescanned.

Menu.createMenu now takes the substitutions the page itself is rendered
with and applies them to both templates. {{root}} is relative to the page,
so {{root}}reference/index.html resolves from every depth. The keys of the
menu go in last, so a template cannot lose one of its own to a project
that happens to define the same name.

An item also carries a Title, offered as {{fsdocs-menu-item-title}}, for
the hover text of a link whose own text does not say where it leads. The
API reference fills it with the full name of a namespace, where the menu
shortened the entry to the part that tells it apart from its neighbours.

The Markdown renderer built the same menu from the same templates and had
none of the recent fixes to it: nothing marked the namespace being read,
nothing marked the section, and its entries carried their full names. It
now matches the HTML renderer.

The list of documents no longer escapes a page title before handing it to
createMenu, which escapes it again, so a title carrying an ampersand
reached the page as the escape sequence itself. The menu templating path
had no test of its own, which is how that survived; it has two now.

Under watch, the menus are built from the substitutions of the moment
rather than the ones the server started with, so a change to a project
file reaches them.

Menu.createMenu takes an argument it did not before, and Menu.MenuItem has
a field it did not before. Both are breaking for code that builds menus
through FSharp.Formatting.Common.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant