@@ -54,13 +54,59 @@ export interface Config {
54
54
footer : string | null ; // defaults to “Built with Observable on [date].”
55
55
toc : TableOfContents ;
56
56
style : null | Style ; // defaults to {theme: ["light", "dark"]}
57
- deploy : null | { workspace : string ; project : string } ;
58
57
search : boolean ; // default to false
59
58
md : MarkdownIt ;
60
59
loaders : LoaderResolver ;
61
60
watchPath ?: string ;
62
61
}
63
62
63
+ interface ConfigSpec {
64
+ root ?: unknown ;
65
+ output ?: unknown ;
66
+ base ?: unknown ;
67
+ sidebar ?: unknown ;
68
+ style ?: unknown ;
69
+ theme ?: unknown ;
70
+ search ?: unknown ;
71
+ scripts ?: unknown ;
72
+ head ?: unknown ;
73
+ header ?: unknown ;
74
+ footer ?: unknown ;
75
+ interpreters ?: unknown ;
76
+ title ?: unknown ;
77
+ pages ?: unknown ;
78
+ pager ?: unknown ;
79
+ toc ?: unknown ;
80
+ linkify ?: unknown ;
81
+ typographer ?: unknown ;
82
+ quotes ?: unknown ;
83
+ cleanUrls ?: unknown ;
84
+ markdownIt ?: unknown ;
85
+ }
86
+
87
+ interface ScriptSpec {
88
+ src ?: unknown ;
89
+ async ?: unknown ;
90
+ type ?: unknown ;
91
+ }
92
+
93
+ interface SectionSpec {
94
+ name ?: unknown ;
95
+ open ?: unknown ;
96
+ collapsible ?: unknown ;
97
+ pages ?: unknown ;
98
+ }
99
+
100
+ interface PageSpec {
101
+ name ?: unknown ;
102
+ path ?: unknown ;
103
+ }
104
+
105
+ interface TableOfContentsSpec {
106
+ label ?: unknown ;
107
+ show ?: unknown ;
108
+ }
109
+
64
110
/**
65
111
* Returns the absolute path to the specified config file, which is specified as a
66
112
* path relative to the given root (if any). If you want to import this, you should
@@ -72,7 +118,7 @@ function resolveConfig(configPath: string, root = "."): string {
72
118
73
119
// By using the modification time of the config, we ensure that we pick up any
74
120
// changes to the config on reload.
75
- async function importConfig ( path : string ) : Promise < any > {
121
+ async function importConfig ( path : string ) : Promise < ConfigSpec > {
76
122
const { mtimeMs} = await stat ( path ) ;
77
123
return ( await import ( `${ pathToFileURL ( path ) . href } ?${ mtimeMs } ` ) ) . default ;
78
124
}
@@ -116,73 +162,63 @@ function readPages(root: string, md: MarkdownIt): Page[] {
116
162
return pages ;
117
163
}
118
164
119
- let currentDate = new Date ( ) ;
165
+ let currentDate : Date | null = null ;
120
166
121
- export function setCurrentDate ( date = new Date ( ) ) : void {
167
+ /** For testing only! */
168
+ export function setCurrentDate ( date : Date | null ) : void {
122
169
currentDate = date ;
123
170
}
124
171
125
172
// The config is used as a cache key for other operations; for example the pages
126
173
// are used as a cache key for search indexing and the previous & next links in
127
174
// the footer. When given the same spec (because import returned the same
128
175
// module), we want to return the same Config instance.
129
- const configCache = new WeakMap < any , Config > ( ) ;
176
+ const configCache = new WeakMap < ConfigSpec , Config > ( ) ;
130
177
131
- export function normalizeConfig ( spec : any = { } , defaultRoot = "docs" , watchPath ?: string ) : Config {
178
+ export function normalizeConfig ( spec : ConfigSpec = { } , defaultRoot = "docs" , watchPath ?: string ) : Config {
132
179
const cachedConfig = configCache . get ( spec ) ;
133
180
if ( cachedConfig ) return cachedConfig ;
134
- let {
135
- root = defaultRoot ,
136
- output = "dist" ,
137
- base = "/" ,
138
- sidebar,
139
- style,
140
- theme = "default" ,
141
- search,
142
- deploy,
143
- scripts = [ ] ,
144
- head = "" ,
145
- header = "" ,
146
- footer = `Built with <a href="https://observablehq.com/" target="_blank">Observable</a> on <a title="${ formatIsoDate (
147
- currentDate
148
- ) } ">${ formatLocaleDate ( currentDate ) } </a>.`,
149
- interpreters
150
- } = spec ;
151
- root = String ( root ) ;
152
- output = String ( output ) ;
153
- base = normalizeBase ( base ) ;
154
- if ( style === null ) style = null ;
155
- else if ( style !== undefined ) style = { path : String ( style ) } ;
156
- else style = { theme : ( theme = normalizeTheme ( theme ) ) } ;
157
- const md = createMarkdownIt ( spec ) ;
158
- let { title, pages, pager = true , toc = true } = spec ;
159
- if ( title !== undefined ) title = String ( title ) ;
160
- if ( pages !== undefined ) pages = normalizePages ( pages ) ;
161
- if ( sidebar !== undefined ) sidebar = Boolean ( sidebar ) ;
162
- pager = Boolean ( pager ) ;
163
- scripts = Array . from ( scripts , normalizeScript ) ;
164
- head = stringOrNull ( head ) ;
165
- header = stringOrNull ( header ) ;
166
- footer = stringOrNull ( footer ) ;
167
- toc = normalizeToc ( toc ) ;
168
- deploy = deploy ? { workspace : String ( deploy . workspace ) . replace ( / ^ @ + / , "" ) , project : String ( deploy . project ) } : null ;
169
- search = Boolean ( search ) ;
170
- interpreters = normalizeInterpreters ( interpreters ) ;
171
- const config = {
181
+ const root = spec . root === undefined ? defaultRoot : String ( spec . root ) ;
182
+ const output = spec . output === undefined ? "dist" : String ( spec . output ) ;
183
+ const base = spec . base === undefined ? "/" : normalizeBase ( spec . base ) ;
184
+ const style =
185
+ spec . style === null
186
+ ? null
187
+ : spec . style !== undefined
188
+ ? { path : String ( spec . style ) }
189
+ : { theme : normalizeTheme ( spec . theme === undefined ? "default" : spec . theme ) } ;
190
+ const md = createMarkdownIt ( {
191
+ linkify : spec . linkify === undefined ? undefined : Boolean ( spec . linkify ) ,
192
+ typographer : spec . typographer === undefined ? undefined : Boolean ( spec . typographer ) ,
193
+ quotes : spec . quotes === undefined ? undefined : ( spec . quotes as any ) ,
194
+ cleanUrls : spec . cleanUrls === undefined ? undefined : Boolean ( spec . cleanUrls ) ,
195
+ markdownIt : spec . markdownIt as any
196
+ } ) ;
197
+ const title = spec . title === undefined ? undefined : String ( spec . title ) ;
198
+ const pages = spec . pages === undefined ? undefined : normalizePages ( spec . pages ) ;
199
+ const pager = spec . pager === undefined ? true : Boolean ( spec . pager ) ;
200
+ const toc = normalizeToc ( spec . toc as any ) ;
201
+ const sidebar = spec . sidebar === undefined ? undefined : Boolean ( spec . sidebar ) ;
202
+ const scripts = spec . scripts === undefined ? [ ] : Array . from ( spec . scripts as any , normalizeScript ) ;
203
+ const head = spec . head === undefined ? "" : stringOrNull ( spec . head ) ;
204
+ const header = spec . header === undefined ? "" : stringOrNull ( spec . header ) ;
205
+ const footer = spec . footer === undefined ? defaultFooter ( ) : stringOrNull ( spec . footer ) ;
206
+ const search = Boolean ( spec . search ) ;
207
+ const interpreters = normalizeInterpreters ( spec . interpreters as any ) ;
208
+ const config : Config = {
172
209
root,
173
210
output,
174
211
base,
175
212
title,
176
- sidebar,
177
- pages,
213
+ sidebar : sidebar ! , // see below
214
+ pages : pages ! , // see below
178
215
pager,
179
216
scripts,
180
217
head,
181
218
header,
182
219
footer,
183
220
toc,
184
221
style,
185
- deploy,
186
222
search,
187
223
md,
188
224
loaders : new LoaderResolver ( { root, interpreters} ) ,
@@ -194,45 +230,49 @@ export function normalizeConfig(spec: any = {}, defaultRoot = "docs", watchPath?
194
230
return config ;
195
231
}
196
232
197
- function normalizeBase ( base : any ) : string {
198
- base = String ( base ) ;
233
+ function defaultFooter ( ) : string {
234
+ const date = currentDate ?? new Date ( ) ;
235
+ return `Built with <a href="https://observablehq.com/" target="_blank">Observable</a> on <a title="${ formatIsoDate (
236
+ date
237
+ ) } ">${ formatLocaleDate ( date ) } </a>.`;
238
+ }
239
+
240
+ function normalizeBase ( spec : unknown ) : string {
241
+ let base = String ( spec ) ;
199
242
if ( ! base . startsWith ( "/" ) ) throw new Error ( `base must start with slash: ${ base } ` ) ;
200
243
if ( ! base . endsWith ( "/" ) ) base += "/" ;
201
244
return base ;
202
245
}
203
246
204
- export function normalizeTheme ( spec : any ) : string [ ] {
205
- return resolveTheme ( typeof spec === "string" ? [ spec ] : spec === null ? [ ] : Array . from ( spec , String ) ) ;
247
+ export function normalizeTheme ( spec : unknown ) : string [ ] {
248
+ return resolveTheme ( typeof spec === "string" ? [ spec ] : spec === null ? [ ] : Array . from ( spec as any , String ) ) ;
206
249
}
207
250
208
- function normalizeScript ( spec : any ) : Script {
209
- if ( typeof spec === "string" ) spec = { src : spec } ;
210
- let { src, async = false , type} = spec ;
211
- src = String ( src ) ;
212
- async = Boolean ( async ) ;
213
- type = type == null ? null : String ( type ) ;
251
+ function normalizeScript ( spec : unknown ) : Script {
252
+ const script = typeof spec === "string" ? { src : spec } : ( spec as ScriptSpec ) ;
253
+ const src = String ( script . src ) ;
254
+ const async = script . async === undefined ? false : Boolean ( script . async ) ;
255
+ const type = script . type == null ? null : String ( script . type ) ;
214
256
return { src, async, type} ;
215
257
}
216
258
217
- function normalizePages ( spec : any ) : Config [ "pages" ] {
218
- return Array . from ( spec , ( spec : any ) =>
219
- "pages" in spec ? normalizeSection ( spec , ( spec : any ) => normalizePage ( spec ) ) : normalizePage ( spec )
259
+ function normalizePages ( spec : unknown ) : Config [ "pages" ] {
260
+ return Array . from ( spec as any , ( spec : SectionSpec | PageSpec ) =>
261
+ "pages" in spec ? normalizeSection ( spec , ( spec : PageSpec ) => normalizePage ( spec ) ) : normalizePage ( spec )
220
262
) ;
221
263
}
222
264
223
- function normalizeSection < T > ( spec : any , normalizePage : ( spec : any ) => T ) : Section < T > {
224
- let { name, open, collapsible = open === undefined ? false : true , pages} = spec ;
225
- name = String ( name ) ;
226
- collapsible = Boolean ( collapsible ) ;
227
- open = collapsible ? Boolean ( open ) : true ;
228
- pages = Array . from ( pages , normalizePage ) ;
265
+ function normalizeSection < T > ( spec : SectionSpec , normalizePage : ( spec : PageSpec ) => T ) : Section < T > {
266
+ const name = String ( spec . name ) ;
267
+ const collapsible = spec . collapsible === undefined ? spec . open !== undefined : Boolean ( spec . collapsible ) ;
268
+ const open = collapsible ? Boolean ( spec . open ) : true ;
269
+ const pages = Array . from ( spec . pages as any , normalizePage ) ;
229
270
return { name, collapsible, open, pages} ;
230
271
}
231
272
232
- function normalizePage ( spec : any ) : Page {
233
- let { name, path} = spec ;
234
- name = String ( name ) ;
235
- path = String ( path ) ;
273
+ function normalizePage ( spec : PageSpec ) : Page {
274
+ const name = String ( spec . name ) ;
275
+ let path = String ( spec . path ) ;
236
276
if ( isAssetPath ( path ) ) {
237
277
const u = parseRelativeUrl ( join ( "/" , path ) ) ; // add leading slash
238
278
let { pathname} = u ;
@@ -243,19 +283,18 @@ function normalizePage(spec: any): Page {
243
283
return { name, path} ;
244
284
}
245
285
246
- function normalizeInterpreters ( spec : any ) : Record < string , string [ ] | null > {
286
+ function normalizeInterpreters ( spec : { [ key : string ] : unknown } = { } ) : { [ key : string ] : string [ ] | null } {
247
287
return Object . fromEntries (
248
- Object . entries < any > ( spec ?? { } ) . map ( ( [ key , value ] ) : [ string , string [ ] | null ] => {
249
- return [ String ( key ) , value == null ? null : Array . from ( value , String ) ] ;
288
+ Object . entries ( spec ) . map ( ( [ key , value ] ) : [ string , string [ ] | null ] => {
289
+ return [ String ( key ) , value == null ? null : Array . from ( value as any , String ) ] ;
250
290
} )
251
291
) ;
252
292
}
253
293
254
- function normalizeToc ( spec : any ) : TableOfContents {
255
- if ( typeof spec === "boolean" ) spec = { show : spec } ;
256
- let { label = "Contents" , show = true } = spec ;
257
- label = String ( label ) ;
258
- show = Boolean ( show ) ;
294
+ function normalizeToc ( spec : TableOfContentsSpec | boolean = true ) : TableOfContents {
295
+ const toc = typeof spec === "boolean" ? { show : spec } : ( spec as TableOfContentsSpec ) ;
296
+ const label = toc . label === undefined ? "Contents" : String ( toc . label ) ;
297
+ const show = toc . show === undefined ? true : Boolean ( toc . show ) ;
259
298
return { label, show} ;
260
299
}
261
300
0 commit comments