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,9 @@ 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 ) href = `${ href } #${ method } ` ;
48+ return `[${ [ symbol , connector , method ] . join ( "" ) } ](${ href } )` ;
4649 } else {
4750 return symbol ;
4851 }
@@ -57,40 +60,60 @@ export function* ApiPage({
5760 content : (
5861 < >
5962 < > { banner } </ >
60- { yield * SymbolHeader ( { pkg, page } ) }
61- { yield * ApiBody ( { page, linkResolver } ) }
63+ { yield * SymbolHeader ( { pkg, page : current } ) }
64+ { yield * ApiBody ( { page : current , linkResolver } ) }
6265 </ >
6366 ) ,
64- linkResolver : createSibling ,
6567 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 ) } ` ;
68+ const { series : allSeries } = yield * useConfig ( ) ;
69+ const series = yield * useApiSeries ( ) ;
70+ const entrypoint = current . experimental ? "./experimental" : "." ;
71+ const suffix = current . experimental ? "/experimental" : "" ;
7072
73+ // Toggle across every series that documents the current symbol:
74+ // stable releases show their version (e.g. 3.6.1, 4.0.3), and the
75+ // prerelease shows as "next" — but only when it's actually newer
76+ // than its stable parent (otherwise there's nothing to switch to).
7177 const links = yield * all (
72- stableSeries . map ( function * ( s ) {
78+ allSeries . map ( function * ( s ) {
7379 const seriesPkg = yield * usePackage ( {
7480 type : "worktree" ,
7581 series : s . name ,
7682 } ) ;
83+
84+ if ( s . includePrerelease ) {
85+ const parent = allSeries . find ( ( p ) => p . name === s . parent ) ;
86+ if ( parent ) {
87+ const parentPkg = yield * usePackage ( {
88+ type : "worktree" ,
89+ series : parent . name ,
90+ } ) ;
91+ if ( ! gt ( seriesPkg . version , parentPkg . version ) ) return null ;
92+ }
93+ }
94+
7795 const seriesDocs = yield * seriesPkg . docs ( ) ;
78- const hasSymbol = seriesDocs [ "." ] . some ( ( node ) =>
79- node . name === current
96+ const hasSymbol = ( seriesDocs [ entrypoint ] ?? [ ] ) . some ( ( node ) =>
97+ node . name === current . name
8098 ) ;
8199
82100 if ( ! hasSymbol ) return null ;
83101
102+ const isCurrent = s . name === series ;
103+ const label = s . includePrerelease ? "next" : seriesPkg . version ;
104+
84105 return (
85106 < a
86- href = { yield * createRootUrl ( `api/${ s . name } ` ) ( current ) }
107+ href = { yield * createRootUrl ( `api/${ s . name } ${ suffix } ` ) (
108+ current . name ,
109+ ) }
87110 class = { `text-base ${
88- s . name === currentSeries
111+ isCurrent
89112 ? "font-bold text-sky-500"
90113 : "text-gray-600 dark:text-gray-400 hover:text-sky-500"
91114 } `}
92115 >
93- { seriesPkg . version }
116+ { label }
94117 </ a >
95118 ) ;
96119 } ) ,
@@ -154,14 +177,12 @@ export function* ApiReference({
154177 content,
155178 current,
156179 pages,
157- linkResolver,
158180 versionToggle,
159181} : {
160182 pkg : Package ;
161183 content : JSXElement ;
162- current : string ;
184+ current : LocalDocPage ;
163185 pages : LocalDocPage [ ] ;
164- linkResolver : ResolveLinkFunction ;
165186 versionToggle : JSXElement ;
166187} ) {
167188 return (
@@ -172,7 +193,7 @@ export function* ApiReference({
172193 < span class = "font-bold" > API Reference</ span >
173194 { versionToggle }
174195 </ h3 >
175- { yield * Menu ( { pages, current, linkResolver } ) }
196+ { yield * Menu ( { pages, current } ) }
176197 </ nav >
177198 </ aside >
178199 < article
@@ -210,30 +231,32 @@ export function* SymbolHeader(
210231function * Menu ( {
211232 pages,
212233 current,
213- linkResolver,
214234} : {
215- current : string ;
235+ current : LocalDocPage ;
216236 pages : LocalDocPage [ ] ;
217- linkResolver : ResolveLinkFunction ;
218237} ) {
219238 const elements = [ ] ;
220239 for ( const page of pages . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ) {
240+ const isCurrent = page . name === current . name &&
241+ ! ! page . experimental === ! ! current . experimental ;
221242 elements . push (
222243 < li >
223- { current === page . name
244+ { isCurrent
224245 ? (
225246 < span class = "rounded px-2 block w-full py-2 bg-gray-100 dark:bg-gray-700 cursor-default " >
226247 < Icon kind = { page . kind } />
227248 { page . name }
249+ { page . experimental ? < ExperimentalBadge class = "ml-1" /> : "" }
228250 </ span >
229251 )
230252 : (
231253 < a
232254 class = "rounded px-2 block w-full py-2 hover:bg-gray-100 dark:hover:bg-gray-800"
233- href = { yield * linkResolver ( page . name ) }
255+ href = { yield * pageHref ( page ) }
234256 >
235257 < Icon kind = { page . kind } />
236258 { page . name }
259+ { page . experimental ? < ExperimentalBadge class = "ml-1" /> : "" }
237260 </ a >
238261 ) }
239262 </ li > ,
0 commit comments