1
1
import mime from "mime" ;
2
- import type { Config , Page , Script , Section } from "./config.js" ;
2
+ import type { Config , Page , Script } from "./config.js" ;
3
3
import { mergeToc } from "./config.js" ;
4
4
import { getClientPath } from "./files.js" ;
5
- import type { Html } from "./html.js" ;
5
+ import type { Html , HtmlResolvers } from "./html.js" ;
6
6
import { html , parseHtml , rewriteHtml } from "./html.js" ;
7
7
import { transpileJavaScript } from "./javascript/transpile.js" ;
8
8
import type { MarkdownPage } from "./markdown.js" ;
@@ -25,9 +25,8 @@ type RenderInternalOptions =
25
25
26
26
export async function renderPage ( page : MarkdownPage , options : RenderOptions & RenderInternalOptions ) : Promise < string > {
27
27
const { data} = page ;
28
- const { root , md , base, path, pages , title, preview, search } = options ;
28
+ const { base, path, title, preview} = options ;
29
29
const { loaders, resolvers = await getResolvers ( page , options ) } = options ;
30
- const { normalizeLink} = md ;
31
30
const sidebar = data ?. sidebar !== undefined ? Boolean ( data . sidebar ) : options . sidebar ;
32
31
const toc = mergeToc ( data ?. toc , options . toc ) ;
33
32
const draft = Boolean ( data ?. draft ) ;
41
40
. filter ( ( title ) : title is string => ! ! title )
42
41
. join ( " | " ) } </ title > \n`
43
42
: ""
44
- } ${ renderHead ( page , resolvers , options ) } ${
43
+ } ${ renderHead ( page . head , resolvers , options ) } ${
45
44
path === "/404"
46
45
? html . unsafe ( `\n<script type="module">
47
46
@@ -69,8 +68,8 @@ import ${preview || page.code.length ? `{${preview ? "open, " : ""}define} from
69
68
files ,
70
69
resolveFile ,
71
70
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 ) )
74
73
) } `
75
74
: ""
76
75
} ${
@@ -83,24 +82,32 @@ import ${preview || page.code.length ? `{${preview ? "open, " : ""}define} from
83
82
${ preview ? `\nopen({hash: ${ JSON . stringify ( resolvers . hash ) } , eval: (body) => eval(body)});\n` : "" } ${ page . code
84
83
. map ( ( { node, id} ) => `\n${ transpileJavaScript ( node , { id, path, resolveImport} ) } ` )
85
84
. join ( "" ) } `) }
86
- </ script > ${ sidebar ? html `\n${ await renderSidebar ( title , pages , root , path , search , normalizeLink ) } ` : "" } ${
85
+ </ script > ${ sidebar ? html `\n${ await renderSidebar ( options ) } ` : "" } ${
87
86
toc . show ? html `\n${ renderToc ( findHeaders ( page ) , toc . label ) } ` : ""
88
87
}
89
- < div id ="observablehq-center "> ${ renderHeader ( options , data ) }
88
+ < div id ="observablehq-center "> ${ renderHeader ( page . header , resolvers ) }
90
89
< 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 ) }
92
91
</ div >
93
92
` ) ;
94
93
}
95
94
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 {
97
100
return Array . from ( files )
98
101
. sort ( )
99
102
. map ( ( f ) => renderFile ( f , resolve , getLastModified ) )
100
103
. join ( "" ) ;
101
104
}
102
105
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 {
104
111
return `\nregisterFile(${ JSON . stringify ( name ) } , ${ JSON . stringify ( {
105
112
name,
106
113
mimeType : mime . getType ( name ) ?? undefined ,
@@ -109,22 +116,17 @@ function renderFile(name: string, resolve: (name: string) => string, getLastModi
109
116
} ) } );`;
110
117
}
111
118
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 ;
120
122
return html `< input id ="observablehq-sidebar-toggle " type ="checkbox " title ="Toggle sidebar ">
121
123
< label id ="observablehq-sidebar-backdrop " for ="observablehq-sidebar-toggle "> </ label >
122
124
< nav id ="observablehq-sidebar ">
123
125
< ol >
124
126
< label id ="observablehq-sidebar-close " for ="observablehq-sidebar-toggle "> </ label >
125
127
< li class ="observablehq-link ${
126
128
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 >
128
130
</ ol > ${
129
131
search
130
132
? html `\n < div id ="observablehq-search "> < input type ="search " placeholder ="Search "> </ div >
@@ -171,7 +173,7 @@ interface Header {
171
173
const tocSelector = "h1:not(:first-of-type), h2:first-child, :not(h1) + h2" ;
172
174
173
175
function findHeaders ( page : MarkdownPage ) : Header [ ] {
174
- return Array . from ( parseHtml ( page . html ) . document . querySelectorAll ( tocSelector ) )
176
+ return Array . from ( parseHtml ( page . body ) . document . querySelectorAll ( tocSelector ) )
175
177
. map ( ( node ) => ( { label : node . textContent , href : node . firstElementChild ?. getAttribute ( "href" ) } ) )
176
178
. filter ( ( d ) : d is Header => ! ! d . label && ! ! d . href ) ;
177
179
}
@@ -198,12 +200,8 @@ function renderListItem(page: Page, path: string, normalizeLink: (href: string)
198
200
} "> < a href ="${ normalizeLink ( relativePath ( path , page . path ) ) } "> ${ page . name } </ a > </ li > `;
199
201
}
200
202
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 ;
207
205
const resolveScript = ( src : string ) => ( / ^ \w + : / . test ( src ) ? src : resolveImport ( relativePath ( root , src ) ) ) ;
208
206
return html `< link rel ="preconnect " href ="https://fonts.gstatic.com " crossorigin > ${
209
207
Array . from ( new Set ( Array . from ( stylesheets , ( i ) => resolveStylesheet ( i ) ) ) , renderStylesheetPreload ) // <link rel=preload as=style>
@@ -212,7 +210,7 @@ function renderHead(
212
210
} ${
213
211
Array . from ( new Set ( Array . from ( staticImports , ( i ) => resolveImport ( i ) ) ) , renderModulePreload ) // <link rel=modulepreload>
214
212
} ${
215
- head ? html `\n${ html . unsafe ( head ) } ` : null // arbitrary user content
213
+ head ? html `\n${ html . unsafe ( rewriteHtml ( head , resolvers ) ) } ` : null // arbitrary user content
216
214
} ${
217
215
Array . from ( scripts , ( s ) => renderScript ( s , resolveScript ) ) // <script src>
218
216
} `;
@@ -236,23 +234,18 @@ function renderModulePreload(href: string): Html {
236
234
return html `\n< link rel ="modulepreload " href ="${ href } "> ` ;
237
235
}
238
236
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 ;
242
241
}
243
242
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 ;
252
245
const link = options . pager ? findLink ( path , options ) : null ;
253
246
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 > ` : ""
256
249
}
257
250
</ footer > `
258
251
: null ;
0 commit comments