1- import { all } from "effection" ;
1+ import { all , type Operation } from "effection" ;
22import type { JSXElement } from "revolution" ;
33import { useConfig } from "../../context/config.ts" ;
44import { LocalDocPage } from "../../hooks/use-deno-doc.tsx" ;
55import { ResolveLinkFunction , useMarkdown } from "../../hooks/use-markdown.tsx" ;
66import { Package , usePackage } from "../../lib/package.ts" ;
7- import { major } from "../../lib/semver.ts" ;
8- import { createRootUrl , createSibling } from "../../lib/links-resolvers.ts" ;
7+ import { gt , major } from "../../lib/semver.ts" ;
8+ import { createRootUrl } from "../../lib/links-resolvers.ts" ;
99import { SourceCodeIcon } from "../icons/source-code.tsx" ;
1010import { GithubPill } from "../package/source-link.tsx" ;
11- import { Icon } from "../type/icon.tsx" ;
11+ import { ExperimentalBadge , Icon } from "../type/icon.tsx" ;
1212import { Type } from "../type/jsx.tsx" ;
1313import { Keyword } from "../type/tokens.tsx" ;
1414
15+ /**
16+ * Root-based URL for a symbol's doc page. Experimental symbols live under a
17+ * `/experimental` segment, so linking cannot be relative to the current page
18+ * (which would resolve into or out of the wrong namespace).
19+ */
20+ function * pageHref (
21+ seriesName : string ,
22+ page : LocalDocPage ,
23+ ) : Operation < string > {
24+ let base = page . experimental
25+ ? `api/${ seriesName } /experimental`
26+ : `api/${ seriesName } ` ;
27+ return yield * createRootUrl ( base ) ( page . name ) ;
28+ }
29+
1530export function * ApiPage ( {
1631 pages,
1732 current,
33+ currentExperimental = false ,
34+ seriesName,
1835 pkg,
19- externalLinkResolver,
2036 banner,
2137} : {
2238 current : string ;
39+ currentExperimental ?: boolean ;
40+ seriesName : string ;
2341 pages : LocalDocPage [ ] ;
2442 pkg : Package ;
2543 banner ?: JSXElement ;
26- externalLinkResolver : ResolveLinkFunction ;
2744} ) {
28- const page = pages . find ( ( node ) => node . name === current ) ;
45+ const page = pages . find ( ( node ) =>
46+ node . name === current && ! ! node . experimental === currentExperimental
47+ ) ;
2948
3049 if ( ! page ) throw new Error ( `Could not find a doc page for ${ current } ` ) ;
3150
@@ -38,11 +57,9 @@ export function* ApiPage({
3857 pages . find ( ( page ) => page . name === symbol ) ;
3958
4059 if ( target ) {
41- return `[${
42- [ symbol , connector , method ] . join (
43- "" ,
44- )
45- } ](${ yield * externalLinkResolver ( symbol , connector , method ) } )`;
60+ let href = yield * pageHref ( seriesName , target ) ;
61+ if ( connector && method ) href = `${ href } #${ method } ` ;
62+ return `[${ [ symbol , connector , method ] . join ( "" ) } ](${ href } )` ;
4663 } else {
4764 return symbol ;
4865 }
@@ -53,6 +70,8 @@ export function* ApiPage({
5370 { yield * ApiReference ( {
5471 pages,
5572 current,
73+ currentExperimental,
74+ seriesName,
5675 pkg,
5776 content : (
5877 < >
@@ -61,36 +80,53 @@ export function* ApiPage({
6180 { yield * ApiBody ( { page, linkResolver } ) }
6281 </ >
6382 ) ,
64- linkResolver : createSibling ,
6583 versionToggle : yield * ( function * ( ) {
6684 const { series } = yield * useConfig ( ) ;
67- // Only show stable series in version toggle (no prereleases)
68- const stableSeries = series . filter ( ( s ) => ! s . includePrerelease ) ;
69- const currentSeries = `v${ major ( pkg . version ) } ` ;
85+ const entrypoint = currentExperimental ? "./experimental" : "." ;
86+ const suffix = currentExperimental ? "/experimental" : "" ;
7087
88+ // Toggle across every series that documents the current symbol:
89+ // stable releases show their version (e.g. 3.6.1, 4.0.3), and the
90+ // prerelease shows as "next" — but only when it's actually newer
91+ // than its stable parent (otherwise there's nothing to switch to).
7192 const links = yield * all (
72- stableSeries . map ( function * ( s ) {
93+ series . map ( function * ( s ) {
7394 const seriesPkg = yield * usePackage ( {
7495 type : "worktree" ,
7596 series : s . name ,
7697 } ) ;
98+
99+ if ( s . includePrerelease ) {
100+ const parent = series . find ( ( p ) => p . name === s . parent ) ;
101+ if ( parent ) {
102+ const parentPkg = yield * usePackage ( {
103+ type : "worktree" ,
104+ series : parent . name ,
105+ } ) ;
106+ if ( ! gt ( seriesPkg . version , parentPkg . version ) ) return null ;
107+ }
108+ }
109+
77110 const seriesDocs = yield * seriesPkg . docs ( ) ;
78- const hasSymbol = seriesDocs [ "." ] . some ( ( node ) =>
111+ const hasSymbol = ( seriesDocs [ entrypoint ] ?? [ ] ) . some ( ( node ) =>
79112 node . name === current
80113 ) ;
81114
82115 if ( ! hasSymbol ) return null ;
83116
117+ const isCurrent = s . name === seriesName ;
118+ const label = s . includePrerelease ? "next" : seriesPkg . version ;
119+
84120 return (
85121 < a
86- href = { yield * createRootUrl ( `api/${ s . name } ` ) ( current ) }
122+ href = { yield * createRootUrl ( `api/${ s . name } ${ suffix } ` ) ( current ) }
87123 class = { `text-base ${
88- s . name === currentSeries
124+ isCurrent
89125 ? "font-bold text-sky-500"
90126 : "text-gray-600 dark:text-gray-400 hover:text-sky-500"
91127 } `}
92128 >
93- { seriesPkg . version }
129+ { label }
94130 </ a >
95131 ) ;
96132 } ) ,
@@ -153,15 +189,17 @@ export function* ApiReference({
153189 pkg,
154190 content,
155191 current,
192+ currentExperimental,
193+ seriesName,
156194 pages,
157- linkResolver,
158195 versionToggle,
159196} : {
160197 pkg : Package ;
161198 content : JSXElement ;
162199 current : string ;
200+ currentExperimental : boolean ;
201+ seriesName : string ;
163202 pages : LocalDocPage [ ] ;
164- linkResolver : ResolveLinkFunction ;
165203 versionToggle : JSXElement ;
166204} ) {
167205 return (
@@ -172,7 +210,7 @@ export function* ApiReference({
172210 < span class = "font-bold" > API Reference</ span >
173211 { versionToggle }
174212 </ h3 >
175- { yield * Menu ( { pages, current, linkResolver } ) }
213+ { yield * Menu ( { pages, current, currentExperimental , seriesName } ) }
176214 </ nav >
177215 </ aside >
178216 < article
@@ -210,30 +248,36 @@ export function* SymbolHeader(
210248function * Menu ( {
211249 pages,
212250 current,
213- linkResolver,
251+ currentExperimental,
252+ seriesName,
214253} : {
215254 current : string ;
255+ currentExperimental : boolean ;
256+ seriesName : string ;
216257 pages : LocalDocPage [ ] ;
217- linkResolver : ResolveLinkFunction ;
218258} ) {
219259 const elements = [ ] ;
220260 for ( const page of pages . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ) {
261+ const isCurrent = current === page . name &&
262+ ! ! page . experimental === currentExperimental ;
221263 elements . push (
222264 < li >
223- { current === page . name
265+ { isCurrent
224266 ? (
225267 < span class = "rounded px-2 block w-full py-2 bg-gray-100 dark:bg-gray-700 cursor-default " >
226268 < Icon kind = { page . kind } />
227269 { page . name }
270+ { page . experimental ? < ExperimentalBadge class = "ml-1" /> : "" }
228271 </ span >
229272 )
230273 : (
231274 < a
232275 class = "rounded px-2 block w-full py-2 hover:bg-gray-100 dark:hover:bg-gray-800"
233- href = { yield * linkResolver ( page . name ) }
276+ href = { yield * pageHref ( seriesName , page ) }
234277 >
235278 < Icon kind = { page . kind } />
236279 { page . name }
280+ { page . experimental ? < ExperimentalBadge class = "ml-1" /> : "" }
237281 </ a >
238282 ) }
239283 </ li > ,
0 commit comments