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 , useApiSeries } 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 ( page : LocalDocPage ) : Operation < string > {
21+ let series = yield * useApiSeries ( ) ;
22+ let base = page . experimental ? `api/${ series } /experimental` : `api/${ series } ` ;
23+ return yield * createRootUrl ( base ) ( page . name ) ;
24+ }
25+
1526export function * ApiPage ( {
1627 pages,
1728 current,
1829 pkg,
19- externalLinkResolver,
2030 banner,
2131} : {
22- current : string ;
32+ current : LocalDocPage ;
2333 pages : LocalDocPage [ ] ;
2434 pkg : Package ;
2535 banner ?: JSXElement ;
26- externalLinkResolver : ResolveLinkFunction ;
2736} ) {
28- const page = pages . find ( ( node ) => node . name === current ) ;
29-
30- if ( ! page ) throw new Error ( `Could not find a doc page for ${ current } ` ) ;
31-
3237 const linkResolver : ResolveLinkFunction = function * resolve (
3338 symbol ,
3439 connector ,
@@ -38,11 +43,11 @@ export function* ApiPage({
3843 pages . find ( ( page ) => page . name === symbol ) ;
3944
4045 if ( target ) {
41- return `[ ${
42- [ symbol , connector , method ] . join (
43- "" ,
44- )
45- } ]( ${ yield * externalLinkResolver ( symbol , connector , method ) } )`;
46+ let href = yield * pageHref ( target ) ;
47+ if ( connector && method ) {
48+ href = ` ${ href } # ${ method } ` ;
49+ }
50+ return `[ ${ [ symbol , connector , method ] . join ( "" ) } ]( ${ href } )`;
4651 } else {
4752 return symbol ;
4853 }
@@ -57,40 +62,64 @@ export function* ApiPage({
5762 content : (
5863 < >
5964 < > { banner } </ >
60- { yield * SymbolHeader ( { pkg, page } ) }
61- { yield * ApiBody ( { page, linkResolver } ) }
65+ { yield * SymbolHeader ( { pkg, page : current } ) }
66+ { yield * ApiBody ( { page : current , linkResolver } ) }
6267 </ >
6368 ) ,
64- linkResolver : createSibling ,
6569 versionToggle : yield * ( function * ( ) {
66- 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 ) } ` ;
70+ const { series : allSeries } = yield * useConfig ( ) ;
71+ const series = yield * useApiSeries ( ) ;
72+ const entrypoint = current . experimental ? "./experimental" : "." ;
73+ const suffix = current . experimental ? "/experimental" : "" ;
7074
75+ // Toggle across every series that documents the current symbol:
76+ // stable releases show their version (e.g. 3.6.1, 4.0.3), and the
77+ // prerelease shows as "next" — but only when it's actually newer
78+ // than its stable parent (otherwise there's nothing to switch to).
7179 const links = yield * all (
72- stableSeries . map ( function * ( s ) {
80+ allSeries . map ( function * ( s ) {
7381 const seriesPkg = yield * usePackage ( {
7482 type : "worktree" ,
7583 series : s . name ,
7684 } ) ;
85+
86+ if ( s . includePrerelease ) {
87+ const parent = allSeries . find ( ( p ) => p . name === s . parent ) ;
88+ if ( parent ) {
89+ const parentPkg = yield * usePackage ( {
90+ type : "worktree" ,
91+ series : parent . name ,
92+ } ) ;
93+ if ( ! gt ( seriesPkg . version , parentPkg . version ) ) {
94+ return null ;
95+ }
96+ }
97+ }
98+
7799 const seriesDocs = yield * seriesPkg . docs ( ) ;
78- const hasSymbol = seriesDocs [ "." ] . some ( ( node ) =>
79- node . name === current
100+ const hasSymbol = ( seriesDocs [ entrypoint ] ?? [ ] ) . some ( ( node ) =>
101+ node . name === current . name
80102 ) ;
81103
82- if ( ! hasSymbol ) return null ;
104+ if ( ! hasSymbol ) {
105+ return null ;
106+ }
107+
108+ const isCurrent = s . name === series ;
109+ const label = s . includePrerelease ? "next" : seriesPkg . version ;
83110
84111 return (
85112 < a
86- href = { yield * createRootUrl ( `api/${ s . name } ` ) ( current ) }
113+ href = { yield * createRootUrl ( `api/${ s . name } ${ suffix } ` ) (
114+ current . name ,
115+ ) }
87116 class = { `text-base ${
88- s . name === currentSeries
117+ isCurrent
89118 ? "font-bold text-sky-500"
90119 : "text-gray-600 dark:text-gray-400 hover:text-sky-500"
91120 } `}
92121 >
93- { seriesPkg . version }
122+ { label }
94123 </ a >
95124 ) ;
96125 } ) ,
@@ -157,14 +186,12 @@ export function* ApiReference({
157186 content,
158187 current,
159188 pages,
160- linkResolver,
161189 versionToggle,
162190} : {
163191 pkg : Package ;
164192 content : JSXElement ;
165- current : string ;
193+ current : LocalDocPage ;
166194 pages : LocalDocPage [ ] ;
167- linkResolver : ResolveLinkFunction ;
168195 versionToggle : JSXElement ;
169196} ) {
170197 return (
@@ -175,7 +202,7 @@ export function* ApiReference({
175202 < span class = "font-bold" > API Reference</ span >
176203 { versionToggle }
177204 </ h3 >
178- { yield * Menu ( { pages, current, linkResolver } ) }
205+ { yield * Menu ( { pages, current } ) }
179206 </ nav >
180207 </ aside >
181208 < article
@@ -213,30 +240,32 @@ export function* SymbolHeader(
213240function * Menu ( {
214241 pages,
215242 current,
216- linkResolver,
217243} : {
218- current : string ;
244+ current : LocalDocPage ;
219245 pages : LocalDocPage [ ] ;
220- linkResolver : ResolveLinkFunction ;
221246} ) {
222247 const elements = [ ] ;
223248 for ( const page of pages . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ) {
249+ const isCurrent = page . name === current . name &&
250+ ! ! page . experimental === ! ! current . experimental ;
224251 elements . push (
225252 < li >
226- { current === page . name
253+ { isCurrent
227254 ? (
228255 < span class = "rounded px-2 block w-full py-2 bg-gray-100 dark:bg-gray-700 cursor-default " >
229256 < Icon kind = { page . kind } />
230257 { page . name }
258+ { page . experimental ? < ExperimentalBadge class = "ml-1" /> : "" }
231259 </ span >
232260 )
233261 : (
234262 < a
235263 class = "rounded px-2 block w-full py-2 hover:bg-gray-100 dark:hover:bg-gray-800"
236- href = { yield * linkResolver ( page . name ) }
264+ href = { yield * pageHref ( page ) }
237265 >
238266 < Icon kind = { page . kind } />
239267 { page . name }
268+ { page . experimental ? < ExperimentalBadge class = "ml-1" /> : "" }
240269 </ a >
241270 ) }
242271 </ li > ,
0 commit comments