11import mime from "mime" ;
2- import type { Config , Page , Script , Section } from "./config.js" ;
2+ import type { Config , Page , Script } from "./config.js" ;
33import { mergeToc } from "./config.js" ;
44import { getClientPath } from "./files.js" ;
5- import type { Html } from "./html.js" ;
5+ import type { Html , HtmlResolvers } from "./html.js" ;
66import { html , parseHtml , rewriteHtml } from "./html.js" ;
77import { transpileJavaScript } from "./javascript/transpile.js" ;
88import type { MarkdownPage } from "./markdown.js" ;
@@ -25,9 +25,8 @@ type RenderInternalOptions =
2525
2626export async function renderPage ( page : MarkdownPage , options : RenderOptions & RenderInternalOptions ) : Promise < string > {
2727 const { data} = page ;
28- const { root , md , base, path, pages , title, preview, search } = options ;
28+ const { base, path, title, preview} = options ;
2929 const { loaders, resolvers = await getResolvers ( page , options ) } = options ;
30- const { normalizeLink} = md ;
3130 const sidebar = data ?. sidebar !== undefined ? Boolean ( data . sidebar ) : options . sidebar ;
3231 const toc = mergeToc ( data ?. toc , options . toc ) ;
3332 const draft = Boolean ( data ?. draft ) ;
4140 . filter ( ( title ) : title is string => ! ! title )
4241 . join ( " | " ) } </ title > \n`
4342 : ""
44- } ${ renderHead ( page , resolvers , options ) } ${
43+ } ${ renderHead ( page . head , resolvers , options ) } ${
4544 path === "/404"
4645 ? html . unsafe ( `\n<script type="module">
4746
@@ -69,8 +68,8 @@ import ${preview || page.code.length ? `{${preview ? "open, " : ""}define} from
6968 files ,
7069 resolveFile ,
7170 preview
72- ? ( name : string ) => loaders . getSourceLastModified ( resolvePath ( path , name ) )
73- : ( name : string ) => loaders . getOutputLastModified ( resolvePath ( path , name ) )
71+ ? ( name ) => loaders . getSourceLastModified ( resolvePath ( path , name ) )
72+ : ( name ) => loaders . getOutputLastModified ( resolvePath ( path , name ) )
7473 ) } `
7574 : ""
7675 } ${
@@ -83,24 +82,32 @@ import ${preview || page.code.length ? `{${preview ? "open, " : ""}define} from
8382${ preview ? `\nopen({hash: ${ JSON . stringify ( resolvers . hash ) } , eval: (body) => eval(body)});\n` : "" } ${ page . code
8483 . map ( ( { node, id} ) => `\n${ transpileJavaScript ( node , { id, path, resolveImport} ) } ` )
8584 . join ( "" ) } `) }
86- </ script > ${ sidebar ? html `\n${ await renderSidebar ( title , pages , root , path , search , normalizeLink ) } ` : "" } ${
85+ </ script > ${ sidebar ? html `\n${ await renderSidebar ( options ) } ` : "" } ${
8786 toc . show ? html `\n${ renderToc ( findHeaders ( page ) , toc . label ) } ` : ""
8887 }
89- < div id ="observablehq-center "> ${ renderHeader ( options , data ) }
88+ < div id ="observablehq-center "> ${ renderHeader ( page . header , resolvers ) }
9089< main id ="observablehq-main " class ="observablehq ${ draft ? " observablehq--draft" : "" } ">
91- ${ html . unsafe ( rewriteHtml ( page . html , resolvers ) ) } </ main > ${ renderFooter ( path , options , data , normalizeLink ) }
90+ ${ html . unsafe ( rewriteHtml ( page . body , resolvers ) ) } </ main > ${ renderFooter ( page . footer , resolvers , options ) }
9291</ div >
9392` ) ;
9493}
9594
96- function renderFiles ( files : Iterable < string > , resolve : ( name : string ) => string , getLastModified ) : string {
95+ function renderFiles (
96+ files : Iterable < string > ,
97+ resolve : ( name : string ) => string ,
98+ getLastModified : ( name : string ) => number | undefined
99+ ) : string {
97100 return Array . from ( files )
98101 . sort ( )
99102 . map ( ( f ) => renderFile ( f , resolve , getLastModified ) )
100103 . join ( "" ) ;
101104}
102105
103- function renderFile ( name : string , resolve : ( name : string ) => string , getLastModified ) : string {
106+ function renderFile (
107+ name : string ,
108+ resolve : ( name : string ) => string ,
109+ getLastModified : ( name : string ) => number | undefined
110+ ) : string {
104111 return `\nregisterFile(${ JSON . stringify ( name ) } , ${ JSON . stringify ( {
105112 name,
106113 mimeType : mime . getType ( name ) ?? undefined ,
@@ -109,22 +116,17 @@ function renderFile(name: string, resolve: (name: string) => string, getLastModi
109116 } ) } );`;
110117}
111118
112- async function renderSidebar (
113- title = "Home" ,
114- pages : ( Page | Section ) [ ] ,
115- root : string ,
116- path : string ,
117- search : boolean ,
118- normalizeLink : ( href : string ) => string
119- ) : Promise < Html > {
119+ async function renderSidebar ( options : RenderOptions ) : Promise < Html > {
120+ const { title = "Home" , pages, root, path, search, md} = options ;
121+ const { normalizeLink} = md ;
120122 return html `< input id ="observablehq-sidebar-toggle " type ="checkbox " title ="Toggle sidebar ">
121123< label id ="observablehq-sidebar-backdrop " for ="observablehq-sidebar-toggle "> </ label >
122124< nav id ="observablehq-sidebar ">
123125 < ol >
124126 < label id ="observablehq-sidebar-close " for ="observablehq-sidebar-toggle "> </ label >
125127 < li class ="observablehq-link ${
126128 normalizePath ( path ) === "/index" ? " observablehq-link-active" : ""
127- } "> < a href ="${ normalizeLink ( relativePath ( path , "/" ) ) } "> ${ title } </ a > </ li >
129+ } "> < a href ="${ md . normalizeLink ( relativePath ( path , "/" ) ) } "> ${ title } </ a > </ li >
128130 </ ol > ${
129131 search
130132 ? html `\n < div id ="observablehq-search "> < input type ="search " placeholder ="Search "> </ div >
@@ -171,7 +173,7 @@ interface Header {
171173const tocSelector = "h1:not(:first-of-type), h2:first-child, :not(h1) + h2" ;
172174
173175function findHeaders ( page : MarkdownPage ) : Header [ ] {
174- return Array . from ( parseHtml ( page . html ) . document . querySelectorAll ( tocSelector ) )
176+ return Array . from ( parseHtml ( page . body ) . document . querySelectorAll ( tocSelector ) )
175177 . map ( ( node ) => ( { label : node . textContent , href : node . firstElementChild ?. getAttribute ( "href" ) } ) )
176178 . filter ( ( d ) : d is Header => ! ! d . label && ! ! d . href ) ;
177179}
@@ -198,12 +200,8 @@ function renderListItem(page: Page, path: string, normalizeLink: (href: string)
198200 } "> < a href ="${ normalizeLink ( relativePath ( path , page . path ) ) } "> ${ page . name } </ a > </ li > `;
199201}
200202
201- function renderHead (
202- parse : MarkdownPage ,
203- { stylesheets, staticImports, resolveImport, resolveStylesheet} : Resolvers ,
204- { scripts, head, root} : RenderOptions
205- ) : Html {
206- if ( parse . data ?. head !== undefined ) head = parse . data . head ;
203+ function renderHead ( head : MarkdownPage [ "head" ] , resolvers : Resolvers , { scripts, root} : RenderOptions ) : Html {
204+ const { stylesheets, staticImports, resolveImport, resolveStylesheet} = resolvers ;
207205 const resolveScript = ( src : string ) => ( / ^ \w + : / . test ( src ) ? src : resolveImport ( relativePath ( root , src ) ) ) ;
208206 return html `< link rel ="preconnect " href ="https://fonts.gstatic.com " crossorigin > ${
209207 Array . from ( new Set ( Array . from ( stylesheets , ( i ) => resolveStylesheet ( i ) ) ) , renderStylesheetPreload ) // <link rel=preload as=style>
@@ -212,7 +210,7 @@ function renderHead(
212210 } ${
213211 Array . from ( new Set ( Array . from ( staticImports , ( i ) => resolveImport ( i ) ) ) , renderModulePreload ) // <link rel=modulepreload>
214212 } ${
215- head ? html `\n${ html . unsafe ( head ) } ` : null // arbitrary user content
213+ head ? html `\n${ html . unsafe ( rewriteHtml ( head , resolvers ) ) } ` : null // arbitrary user content
216214 } ${
217215 Array . from ( scripts , ( s ) => renderScript ( s , resolveScript ) ) // <script src>
218216 } `;
@@ -236,23 +234,18 @@ function renderModulePreload(href: string): Html {
236234 return html `\n< link rel ="modulepreload " href ="${ href } "> ` ;
237235}
238236
239- function renderHeader ( { header} : Pick < Config , "header" > , data : MarkdownPage [ "data" ] ) : Html | null {
240- if ( data ?. header !== undefined ) header = data ?. header ;
241- return header ? html `\n< header id ="observablehq-header "> \n${ html . unsafe ( header ) } \n</ header > ` : null ;
237+ function renderHeader ( header : MarkdownPage [ "header" ] , resolvers : HtmlResolvers ) : Html | null {
238+ return header
239+ ? html `\n< header id ="observablehq-header "> \n${ html . unsafe ( rewriteHtml ( header , resolvers ) ) } \n</ header > `
240+ : null ;
242241}
243242
244- function renderFooter (
245- path : string ,
246- options : Pick < Config , "pages" | "pager" | "title" | "footer" > ,
247- data : MarkdownPage [ "data" ] ,
248- normalizeLink : ( href : string ) => string
249- ) : Html | null {
250- let footer = options . footer ;
251- if ( data ?. footer !== undefined ) footer = data ?. footer ;
243+ function renderFooter ( footer : MarkdownPage [ "footer" ] , resolvers : HtmlResolvers , options : RenderOptions ) : Html | null {
244+ const { path, md} = options ;
252245 const link = options . pager ? findLink ( path , options ) : null ;
253246 return link || footer
254- ? html `\n< footer id ="observablehq-footer "> ${ link ? renderPager ( path , link , normalizeLink ) : "" } ${
255- footer ? html `\n< div > ${ html . unsafe ( footer ) } </ div > ` : ""
247+ ? html `\n< footer id ="observablehq-footer "> ${ link ? renderPager ( path , link , md . normalizeLink ) : "" } ${
248+ footer ? html `\n< div > ${ html . unsafe ( rewriteHtml ( footer , resolvers ) ) } </ div > ` : ""
256249 }
257250</ footer > `
258251 : null ;
0 commit comments