@@ -29,109 +29,50 @@ const typeParser = require('./type-parser.js');
29
29
30
30
module . exports = toHTML ;
31
31
32
- const STABILITY_TEXT_REG_EXP = / ( .* : ) \s * ( \d ) ( [ \s \S ] * ) / ;
33
- const DOC_CREATED_REG_EXP = / < ! - - \s * i n t r o d u c e d _ i n \s * = \s * v ( [ 0 - 9 ] + ) \. ( [ 0 - 9 ] + ) \. ( [ 0 - 9 ] + ) \s * - - > / ;
34
-
35
- // Customized heading without id attribute.
32
+ // Make `marked` to not automatically insert id attributes in headings.
36
33
const renderer = new marked . Renderer ( ) ;
37
- renderer . heading = function ( text , level ) {
38
- return `<h${ level } >${ text } </h${ level } >\n` ;
39
- } ;
40
- marked . setOptions ( {
41
- renderer : renderer
42
- } ) ;
34
+ renderer . heading = ( text , level ) => `<h${ level } >${ text } </h${ level } >\n` ;
35
+ marked . setOptions ( { renderer } ) ;
43
36
44
37
const docPath = path . resolve ( __dirname , '..' , '..' , 'doc' ) ;
45
38
46
39
const gtocPath = path . join ( docPath , 'api' , '_toc.md' ) ;
47
40
const gtocMD = fs . readFileSync ( gtocPath , 'utf8' ) . replace ( / ^ @ \/ \/ .* $ / gm, '' ) ;
48
41
const gtocHTML = marked ( gtocMD ) . replace (
49
42
/ < a h r e f = " ( .* ?) " / g,
50
- ( all , href ) => `<a class="nav-${ toID ( href ) } " href="${ href } "`
43
+ ( all , href ) => `<a class="nav-${ href . replace ( '.html' , '' )
44
+ . replace ( / \W + / g, '-' ) } " href="${ href } "`
51
45
) ;
52
46
53
47
const templatePath = path . join ( docPath , 'template.html' ) ;
54
48
const template = fs . readFileSync ( templatePath , 'utf8' ) ;
55
49
56
- var docCreated = null ;
57
- var nodeVersion = null ;
58
-
59
- /**
60
- * opts: input, filename, nodeVersion.
61
- */
62
- function toHTML ( opts , cb ) {
63
- nodeVersion = opts . nodeVersion || process . version ;
64
- docCreated = opts . input . match ( DOC_CREATED_REG_EXP ) ;
65
-
66
- const lexed = marked . lexer ( opts . input ) ;
67
- render ( {
68
- lexed : lexed ,
69
- filename : opts . filename ,
70
- template : template ,
71
- nodeVersion : nodeVersion ,
72
- analytics : opts . analytics ,
73
- } , cb ) ;
74
- }
75
-
76
- function toID ( filename ) {
77
- return filename
78
- . replace ( '.html' , '' )
79
- . replace ( / [ ^ \w - ] / g, '-' )
80
- . replace ( / - + / g, '-' ) ;
81
- }
82
-
83
- /**
84
- * opts: lexed, filename, template, nodeVersion.
85
- */
86
- function render ( opts , cb ) {
87
- var { lexed, filename, template } = opts ;
88
- const nodeVersion = opts . nodeVersion || process . version ;
50
+ function toHTML ( { input, filename, nodeVersion, analytics } , cb ) {
51
+ filename = path . basename ( filename , '.md' ) ;
89
52
90
- // Get the section.
91
- const section = getSection ( lexed ) ;
53
+ const lexed = marked . lexer ( input ) ;
92
54
93
- filename = path . basename ( filename , '.md' ) ;
55
+ const firstHeading = lexed . find ( ( { type } ) => type === 'heading' ) ;
56
+ const section = firstHeading ? firstHeading . text : 'Index' ;
94
57
95
- parseText ( lexed ) ;
96
- lexed = preprocessElements ( lexed ) ;
97
-
98
- // Generate the table of contents.
99
- // This mutates the lexed contents in-place.
100
- buildToc ( lexed , filename , function ( er , toc ) {
101
- if ( er ) return cb ( er ) ;
102
-
103
- const id = toID ( path . basename ( filename ) ) ;
104
-
105
- template = template . replace ( / _ _ I D _ _ / g, id ) ;
106
- template = template . replace ( / _ _ F I L E N A M E _ _ / g, filename ) ;
107
- template = template . replace ( / _ _ S E C T I O N _ _ / g, section || 'Index' ) ;
108
- template = template . replace ( / _ _ V E R S I O N _ _ / g, nodeVersion ) ;
109
- template = template . replace ( / _ _ T O C _ _ / g, toc ) ;
110
- template = template . replace (
111
- / _ _ G T O C _ _ / g,
112
- gtocHTML . replace ( `class="nav-${ id } ` , `class="nav-${ id } active` )
113
- ) ;
114
-
115
- if ( opts . analytics ) {
116
- template = template . replace (
117
- '<!-- __TRACKING__ -->' ,
118
- analyticsScript ( opts . analytics )
119
- ) ;
120
- }
58
+ preprocessText ( lexed ) ;
59
+ preprocessElements ( lexed ) ;
121
60
122
- template = template . replace ( / _ _ A L T D O C S _ _ / , altDocs ( filename ) ) ;
61
+ // Generate the table of contents. This mutates the lexed contents in-place.
62
+ const toc = buildToc ( lexed , filename ) ;
123
63
124
- // Content has to be the last thing we do with the lexed tokens,
125
- // because it's destructive.
126
- const content = marked . parser ( lexed ) ;
127
- template = template . replace ( / _ _ C O N T E N T _ _ / g, content ) ;
64
+ const id = filename . replace ( / \W + / g, '-' ) ;
128
65
129
- cb ( null , template ) ;
130
- } ) ;
131
- }
66
+ let HTML = template . replace ( '__ID__' , id )
67
+ . replace ( / _ _ F I L E N A M E _ _ / g, filename )
68
+ . replace ( '__SECTION__' , section )
69
+ . replace ( / _ _ V E R S I O N _ _ / g, nodeVersion )
70
+ . replace ( '__TOC__' , toc )
71
+ . replace ( '__GTOC__' , gtocHTML . replace (
72
+ `class="nav-${ id } ` , `class="nav-${ id } active` ) ) ;
132
73
133
- function analyticsScript ( analytics ) {
134
- return `
74
+ if ( analytics ) {
75
+ HTML = HTML . replace ( '<!-- __TRACKING__ -->' , `
135
76
<script src="assets/dnt_helper.js"></script>
136
77
<script>
137
78
if (!_dntEnabled()) {
@@ -143,149 +84,143 @@ function analyticsScript(analytics) {
143
84
ga('create', '${ analytics } ', 'auto');
144
85
ga('send', 'pageview');
145
86
}
146
- </script>
147
- ` ;
148
- }
149
-
150
- // Replace placeholders in text tokens.
151
- function replaceInText ( text ) {
152
- return linkJsTypeDocs ( linkManPages ( text ) ) ;
153
- }
154
-
155
- function altDocs ( filename ) {
156
- if ( ! docCreated ) {
157
- console . error ( `Failed to add alternative version links to ${ filename } ` ) ;
158
- return '' ;
159
- }
160
-
161
- function lte ( v ) {
162
- const ns = v . num . split ( '.' ) ;
163
- if ( docCreated [ 1 ] > + ns [ 0 ] )
164
- return false ;
165
- if ( docCreated [ 1 ] < + ns [ 0 ] )
166
- return true ;
167
- return docCreated [ 2 ] <= + ns [ 1 ] ;
87
+ </script>` ) ;
168
88
}
169
89
170
- const versions = [
171
- { num : '10.x' } ,
172
- { num : '9.x' } ,
173
- { num : '8.x' , lts : true } ,
174
- { num : '7.x' } ,
175
- { num : '6.x' , lts : true } ,
176
- { num : '5.x' } ,
177
- { num : '4.x' , lts : true } ,
178
- { num : '0.12.x' } ,
179
- { num : '0.10.x' }
180
- ] ;
181
-
182
- const host = 'https://nodejs.org' ;
183
- const href = ( v ) => `${ host } /docs/latest-v${ v . num } /api/${ filename } .html` ;
184
-
185
- function li ( v ) {
186
- let html = `<li><a href="${ href ( v ) } ">${ v . num } ` ;
187
-
188
- if ( v . lts )
189
- html += ' <b>LTS</b>' ;
190
-
191
- return html + '</a></li>' ;
90
+ const docCreated = input . match (
91
+ / < ! - - \s * i n t r o d u c e d _ i n \s * = \s * v ( [ 0 - 9 ] + ) \. ( [ 0 - 9 ] + ) \. [ 0 - 9 ] + \s * - - > / ) ;
92
+ if ( docCreated ) {
93
+ HTML = HTML . replace ( '__ALTDOCS__' , altDocs ( filename , docCreated ) ) ;
94
+ } else {
95
+ console . error ( `Failed to add alternative version links to ${ filename } ` ) ;
96
+ HTML = HTML . replace ( '__ALTDOCS__' , '' ) ;
192
97
}
193
98
194
- const lis = versions . filter ( lte ) . map ( li ) . join ( '\n' ) ;
99
+ // Content insertion has to be the last thing we do with the lexed tokens,
100
+ // because it's destructive.
101
+ HTML = HTML . replace ( '__CONTENT__' , marked . parser ( lexed ) ) ;
195
102
196
- if ( ! lis . length )
197
- return '' ;
198
-
199
- return `
200
- <li class="version-picker">
201
- <a href="#">View another version <span>▼</span></a>
202
- <ol class="version-picker">${ lis } </ol>
203
- </li>
204
- ` ;
103
+ cb ( null , HTML ) ;
205
104
}
206
105
207
106
// Handle general body-text replacements.
208
107
// For example, link man page references to the actual page.
209
- function parseText ( lexed ) {
210
- lexed . forEach ( function ( tok ) {
211
- if ( tok . type === 'table' ) {
212
- if ( tok . cells ) {
213
- tok . cells . forEach ( ( row , x ) => {
214
- row . forEach ( ( _ , y ) => {
215
- if ( tok . cells [ x ] && tok . cells [ x ] [ y ] ) {
216
- tok . cells [ x ] [ y ] = replaceInText ( tok . cells [ x ] [ y ] ) ;
217
- }
218
- } ) ;
219
- } ) ;
108
+ function preprocessText ( lexed ) {
109
+ lexed . forEach ( ( token ) => {
110
+ if ( token . type === 'table' ) {
111
+ if ( token . header ) {
112
+ token . header = token . header . map ( replaceInText ) ;
220
113
}
221
114
222
- if ( tok . header ) {
223
- tok . header . forEach ( ( _ , i ) => {
224
- if ( tok . header [ i ] ) {
225
- tok . header [ i ] = replaceInText ( tok . header [ i ] ) ;
226
- }
115
+ if ( token . cells ) {
116
+ token . cells . forEach ( ( row , i ) => {
117
+ token . cells [ i ] = row . map ( replaceInText ) ;
227
118
} ) ;
228
119
}
229
- } else if ( tok . text && tok . type !== 'code' ) {
230
- tok . text = replaceInText ( tok . text ) ;
120
+ } else if ( token . text && token . type !== 'code' ) {
121
+ token . text = replaceInText ( token . text ) ;
231
122
}
232
123
} ) ;
233
124
}
234
125
126
+ // Replace placeholders in text tokens.
127
+ function replaceInText ( text ) {
128
+ if ( text === '' ) return text ;
129
+ return linkJsTypeDocs ( linkManPages ( text ) ) ;
130
+ }
131
+
132
+ // Syscalls which appear in the docs, but which only exist in BSD / macOS.
133
+ const BSD_ONLY_SYSCALLS = new Set ( [ 'lchmod' ] ) ;
134
+ const MAN_PAGE = / ( ^ | \s ) ( [ a - z . ] + ) \( ( \d ) ( [ a - z ] ? ) \) / gm;
135
+
136
+ // Handle references to man pages, eg "open(2)" or "lchmod(2)".
137
+ // Returns modified text, with such refs replaced with HTML links, for example
138
+ // '<a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>'.
139
+ function linkManPages ( text ) {
140
+ return text . replace (
141
+ MAN_PAGE , ( match , beginning , name , number , optionalCharacter ) => {
142
+ // Name consists of lowercase letters,
143
+ // number is a single digit with an optional lowercase letter.
144
+ const displayAs = `${ name } (${ number } ${ optionalCharacter } )` ;
145
+
146
+ if ( BSD_ONLY_SYSCALLS . has ( name ) ) {
147
+ return `${ beginning } <a href="https://www.freebsd.org/cgi/man.cgi` +
148
+ `?query=${ name } &sektion=${ number } ">${ displayAs } </a>` ;
149
+ }
150
+ return `${ beginning } <a href="http://man7.org/linux/man-pages/man${ number } ` +
151
+ `/${ name } .${ number } ${ optionalCharacter } .html">${ displayAs } </a>` ;
152
+ } ) ;
153
+ }
154
+
155
+ const TYPE_SIGNATURE = / \{ [ ^ } ] + \} / g;
156
+ function linkJsTypeDocs ( text ) {
157
+ const parts = text . split ( '`' ) ;
158
+
159
+ // Handle types, for example the source Markdown might say
160
+ // "This argument should be a {number} or {string}".
161
+ for ( let i = 0 ; i < parts . length ; i += 2 ) {
162
+ const typeMatches = parts [ i ] . match ( TYPE_SIGNATURE ) ;
163
+ if ( typeMatches ) {
164
+ typeMatches . forEach ( ( typeMatch ) => {
165
+ parts [ i ] = parts [ i ] . replace ( typeMatch , typeParser . toLink ( typeMatch ) ) ;
166
+ } ) ;
167
+ }
168
+ }
169
+
170
+ return parts . join ( '`' ) ;
171
+ }
172
+
235
173
// Preprocess stability blockquotes and YAML blocks.
236
- function preprocessElements ( input ) {
237
- var state = null ;
238
- const output = [ ] ;
174
+ function preprocessElements ( lexed ) {
175
+ const STABILITY_RE = / ( . * : ) \s * ( \d ) ( [ \s \S ] * ) / ;
176
+ let state = null ;
239
177
let headingIndex = - 1 ;
240
178
let heading = null ;
241
179
242
- output . links = input . links ;
243
- input . forEach ( function ( tok , index ) {
244
- if ( tok . type === 'heading' ) {
180
+ lexed . forEach ( ( token , index ) => {
181
+ if ( token . type === 'heading' ) {
245
182
headingIndex = index ;
246
- heading = tok ;
183
+ heading = token ;
247
184
}
248
- if ( tok . type === 'html' && common . isYAMLBlock ( tok . text ) ) {
249
- tok . text = parseYAML ( tok . text ) ;
185
+ if ( token . type === 'html' && common . isYAMLBlock ( token . text ) ) {
186
+ token . text = parseYAML ( token . text ) ;
250
187
}
251
- if ( tok . type === 'blockquote_start' ) {
188
+ if ( token . type === 'blockquote_start' ) {
252
189
state = 'MAYBE_STABILITY_BQ' ;
253
- return ;
190
+ lexed [ index ] = { type : 'space' } ;
254
191
}
255
- if ( tok . type === 'blockquote_end' && state === 'MAYBE_STABILITY_BQ' ) {
192
+ if ( token . type === 'blockquote_end' && state === 'MAYBE_STABILITY_BQ' ) {
256
193
state = null ;
257
- return ;
194
+ lexed [ index ] = { type : 'space' } ;
258
195
}
259
- if ( tok . type === 'paragraph' && state === 'MAYBE_STABILITY_BQ' ) {
260
- if ( tok . text . match ( / S t a b i l i t y : .* / g) ) {
261
- const stabilityMatch = tok . text . match ( STABILITY_TEXT_REG_EXP ) ;
262
- const stability = Number ( stabilityMatch [ 2 ] ) ;
196
+ if ( token . type === 'paragraph' && state === 'MAYBE_STABILITY_BQ' ) {
197
+ if ( token . text . includes ( 'Stability:' ) ) {
198
+ const [ , prefix , number , explication ] = token . text . match ( STABILITY_RE ) ;
263
199
const isStabilityIndex =
264
200
index - 2 === headingIndex || // General.
265
201
index - 3 === headingIndex ; // With api_metadata block.
266
202
267
203
if ( heading && isStabilityIndex ) {
268
- heading . stability = stability ;
204
+ heading . stability = number ;
269
205
headingIndex = - 1 ;
270
206
heading = null ;
271
207
}
272
- tok . text = parseAPIHeader ( tok . text ) . replace ( / \n / g, ' ' ) ;
273
- output . push ( { type : 'html' , text : tok . text } ) ;
274
- return ;
208
+ token . text = `<div class="api_stability api_stability_${ number } ">` +
209
+ '<a href="documentation.html#documentation_stability_index">' +
210
+ `${ prefix } ${ number } </a>${ explication } </div>`
211
+ . replace ( / \n / g, ' ' ) ;
212
+ lexed [ index ] = { type : 'html' , text : token . text } ;
275
213
} else if ( state === 'MAYBE_STABILITY_BQ' ) {
276
- output . push ( { type : 'blockquote_start' } ) ;
277
214
state = null ;
215
+ lexed [ index - 1 ] = { type : 'blockquote_start' } ;
278
216
}
279
217
}
280
- output . push ( tok ) ;
281
218
} ) ;
282
-
283
- return output ;
284
219
}
285
220
286
221
function parseYAML ( text ) {
287
222
const meta = common . extractAndParseYAML ( text ) ;
288
- const html = [ '<div class="api_metadata">' ] ;
223
+ let html = '<div class="api_metadata">\n' ;
289
224
290
225
const added = { description : '' } ;
291
226
const deprecated = { description : '' } ;
@@ -302,159 +237,130 @@ function parseYAML(text) {
302
237
}
303
238
304
239
if ( meta . changes . length > 0 ) {
305
- let changes = meta . changes . slice ( ) ;
306
- if ( added . description ) changes . push ( added ) ;
307
- if ( deprecated . description ) changes . push ( deprecated ) ;
240
+ if ( added . description ) meta . changes . push ( added ) ;
241
+ if ( deprecated . description ) meta . changes . push ( deprecated ) ;
308
242
309
- changes = changes . sort ( ( a , b ) => versionSort ( a . version , b . version ) ) ;
243
+ meta . changes . sort ( ( a , b ) => versionSort ( a . version , b . version ) ) ;
310
244
311
- html . push ( '<details class="changelog"><summary>History</summary>' ) ;
312
- html . push ( '<table>' ) ;
313
- html . push ( '<tr><th>Version</th><th>Changes</th></tr>' ) ;
245
+ html += '<details class="changelog"><summary>History</summary>\n' +
246
+ '<table>\n<tr><th>Version</th><th>Changes</th></tr>\n' ;
314
247
315
- changes . forEach ( ( change ) => {
316
- html . push ( `<tr><td>${ change . version } </td>` ) ;
317
- html . push ( `<td>${ marked ( change . description ) } </td></tr>` ) ;
248
+ meta . changes . forEach ( ( change ) => {
249
+ html += `<tr><td>${ change . version } </td>\n` +
250
+ `<td>${ marked ( change . description ) } </td></tr>\n` ;
318
251
} ) ;
319
252
320
- html . push ( '</table>' ) ;
321
- html . push ( '</details>' ) ;
253
+ html += '</table>\n</details>\n' ;
322
254
} else {
323
- html . push ( `${ added . description } ${ deprecated . description } ` ) ;
324
- }
325
-
326
- html . push ( '</div>' ) ;
327
- return html . join ( '\n' ) ;
328
- }
329
-
330
- // Syscalls which appear in the docs, but which only exist in BSD / macOS.
331
- const BSD_ONLY_SYSCALLS = new Set ( [ 'lchmod' ] ) ;
332
-
333
- // Handle references to man pages, eg "open(2)" or "lchmod(2)".
334
- // Returns modified text, with such refs replaced with HTML links, for example
335
- // '<a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>'.
336
- function linkManPages ( text ) {
337
- return text . replace (
338
- / ( ^ | \s ) ( [ a - z . ] + ) \( ( \d ) ( [ a - z ] ? ) \) / gm,
339
- ( match , beginning , name , number , optionalCharacter ) => {
340
- // Name consists of lowercase letters, number is a single digit.
341
- const displayAs = `${ name } (${ number } ${ optionalCharacter } )` ;
342
- if ( BSD_ONLY_SYSCALLS . has ( name ) ) {
343
- return `${ beginning } <a href="https://www.freebsd.org/cgi/man.cgi?query=${ name } ` +
344
- `&sektion=${ number } ">${ displayAs } </a>` ;
345
- } else {
346
- return `${ beginning } <a href="http://man7.org/linux/man-pages/man${ number } ` +
347
- `/${ name } .${ number } ${ optionalCharacter } .html">${ displayAs } </a>` ;
348
- }
349
- } ) ;
350
- }
351
-
352
- function linkJsTypeDocs ( text ) {
353
- const parts = text . split ( '`' ) ;
354
- var i ;
355
- var typeMatches ;
356
-
357
- // Handle types, for example the source Markdown might say
358
- // "This argument should be a {Number} or {String}".
359
- for ( i = 0 ; i < parts . length ; i += 2 ) {
360
- typeMatches = parts [ i ] . match ( / \{ ( [ ^ } ] + ) \} / g) ;
361
- if ( typeMatches ) {
362
- typeMatches . forEach ( function ( typeMatch ) {
363
- parts [ i ] = parts [ i ] . replace ( typeMatch , typeParser . toLink ( typeMatch ) ) ;
364
- } ) ;
365
- }
255
+ html += `${ added . description } ${ deprecated . description } \n` ;
366
256
}
367
257
368
- // TODO: maybe put more stuff here?
369
- return parts . join ( '`' ) ;
258
+ html += '</div>' ;
259
+ return html ;
370
260
}
371
261
372
- function parseAPIHeader ( text ) {
373
- const classNames = 'api_stability api_stability_$2' ;
374
- const docsUrl = 'documentation.html#documentation_stability_index' ;
375
-
376
- text = text . replace (
377
- STABILITY_TEXT_REG_EXP ,
378
- `<div class="${ classNames } "><a href="${ docsUrl } ">$1 $2</a>$3</div>`
379
- ) ;
380
- return text ;
381
- }
382
-
383
- // Section is just the first heading.
384
- function getSection ( lexed ) {
385
- for ( var i = 0 , l = lexed . length ; i < l ; i ++ ) {
386
- var tok = lexed [ i ] ;
387
- if ( tok . type === 'heading' ) return tok . text ;
388
- }
389
- return '' ;
390
- }
391
-
392
- function getMark ( anchor ) {
393
- return `<span><a class="mark" href="#${ anchor } " id="${ anchor } ">#</a></span>` ;
262
+ const numberRe = / ^ \d * / ;
263
+ function versionSort ( a , b ) {
264
+ a = a . trim ( ) ;
265
+ b = b . trim ( ) ;
266
+ let i = 0 ; // Common prefix length.
267
+ while ( i < a . length && i < b . length && a [ i ] === b [ i ] ) i ++ ;
268
+ a = a . substr ( i ) ;
269
+ b = b . substr ( i ) ;
270
+ return + b . match ( numberRe ) [ 0 ] - + a . match ( numberRe ) [ 0 ] ;
394
271
}
395
272
396
- function buildToc ( lexed , filename , cb ) {
397
- var toc = [ ] ;
398
- var depth = 0 ;
399
-
273
+ function buildToc ( lexed , filename ) {
400
274
const startIncludeRefRE = / ^ \s * < ! - - \[ s t a r t - i n c l u d e : ( .+ ) \] - - > \s * $ / ;
401
- const endIncludeRefRE = / ^ \s * < ! - - \[ e n d - i n c l u d e : ( . + ) \] - - > \s * $ / ;
275
+ const endIncludeRefRE = / ^ \s * < ! - - \[ e n d - i n c l u d e : . + \] - - > \s * $ / ;
402
276
const realFilenames = [ filename ] ;
403
-
404
- lexed . forEach ( function ( tok ) {
405
- // Keep track of the current filename along @include directives.
406
- if ( tok . type === 'html' ) {
407
- let match ;
408
- if ( ( match = tok . text . match ( startIncludeRefRE ) ) !== null )
409
- realFilenames . unshift ( match [ 1 ] ) ;
410
- else if ( tok . text . match ( endIncludeRefRE ) )
277
+ const idCounters = Object . create ( null ) ;
278
+ let toc = '' ;
279
+ let depth = 0 ;
280
+
281
+ lexed . forEach ( ( token ) => {
282
+ // Keep track of the current filename along comment wrappers of inclusions.
283
+ if ( token . type === 'html' ) {
284
+ const [ , includedFileName ] = token . text . match ( startIncludeRefRE ) || [ ] ;
285
+ if ( includedFileName !== undefined )
286
+ realFilenames . unshift ( includedFileName ) ;
287
+ else if ( endIncludeRefRE . test ( token . text ) )
411
288
realFilenames . shift ( ) ;
412
289
}
413
290
414
- if ( tok . type !== 'heading' ) return ;
415
- if ( tok . depth - depth > 1 ) {
416
- return cb ( new Error ( 'Inappropriate heading level\n' +
417
- JSON . stringify ( tok ) ) ) ;
291
+ if ( token . type !== 'heading' ) return ;
292
+
293
+ if ( token . depth - depth > 1 ) {
294
+ throw new Error ( `Inappropriate heading level:\n ${ JSON . stringify ( token ) } ` ) ;
418
295
}
419
296
420
- depth = tok . depth ;
297
+ depth = token . depth ;
421
298
const realFilename = path . basename ( realFilenames [ 0 ] , '.md' ) ;
422
- const apiName = tok . text . trim ( ) ;
423
- const id = getId ( `${ realFilename } _${ apiName } ` ) ;
424
- toc . push ( new Array ( ( depth - 1 ) * 2 + 1 ) . join ( ' ' ) +
425
- `* <span class="stability_${ tok . stability } ">` +
426
- `<a href="#${ id } ">${ tok . text } </a></span>` ) ;
427
- tok . text += getMark ( id ) ;
428
- if ( realFilename === 'errors' && apiName . startsWith ( 'ERR_' ) ) {
429
- tok . text += getMark ( apiName ) ;
299
+ const headingText = token . text . trim ( ) ;
300
+ const id = getId ( `${ realFilename } _${ headingText } ` , idCounters ) ;
301
+ toc += ' ' . repeat ( ( depth - 1 ) * 2 ) +
302
+ `* <span class="stability_${ token . stability } ">` +
303
+ `<a href="#${ id } ">${ token . text } </a></span>\n` ;
304
+ token . text += `<span><a class="mark" href="#${ id } " id="${ id } ">#</a></span>` ;
305
+ if ( realFilename === 'errors' && headingText . startsWith ( 'ERR_' ) ) {
306
+ token . text += `<span><a class="mark" href="#${ headingText } " ` +
307
+ `id="${ headingText } ">#</a></span>` ;
430
308
}
431
309
} ) ;
432
310
433
- toc = marked . parse ( toc . join ( '\n' ) ) ;
434
- cb ( null , toc ) ;
311
+ return marked ( toc ) ;
435
312
}
436
313
437
- const idCounters = { } ;
438
- function getId ( text ) {
439
- text = text . toLowerCase ( ) ;
440
- text = text . replace ( / [ ^ a - z 0 - 9 ] + / g , '_' ) ;
441
- text = text . replace ( / ^ _ + | _ + $ / , '' ) ;
442
- text = text . replace ( / ^ ( [ ^ a - z ] ) / , '_$1' ) ;
443
- if ( idCounters . hasOwnProperty ( text ) ) {
444
- text += `_ ${ ++ idCounters [ text ] } ` ;
445
- } else {
446
- idCounters [ text ] = 0 ;
314
+ const notAlphaNumerics = / [ ^ a - z 0 - 9 ] + / g ;
315
+ const edgeUnderscores = / ^ _ + | _ + $ / g ;
316
+ const notAlphaStart = / ^ [ ^ a - z ] / ;
317
+ function getId ( text , idCounters ) {
318
+ text = text . toLowerCase ( )
319
+ . replace ( notAlphaNumerics , '_' )
320
+ . replace ( edgeUnderscores , '' )
321
+ . replace ( notAlphaStart , '_$&' ) ;
322
+ if ( idCounters [ text ] !== undefined ) {
323
+ return ` ${ text } _ ${ ++ idCounters [ text ] } ` ;
447
324
}
325
+ idCounters [ text ] = 0 ;
448
326
return text ;
449
327
}
450
328
451
- const numberRe = / ^ ( \d * ) / ;
452
- function versionSort ( a , b ) {
453
- a = a . trim ( ) ;
454
- b = b . trim ( ) ;
455
- let i = 0 ; // Common prefix length.
456
- while ( i < a . length && i < b . length && a [ i ] === b [ i ] ) i ++ ;
457
- a = a . substr ( i ) ;
458
- b = b . substr ( i ) ;
459
- return + b . match ( numberRe ) [ 1 ] - + a . match ( numberRe ) [ 1 ] ;
329
+ function altDocs ( filename , docCreated ) {
330
+ const [ , docCreatedMajor , docCreatedMinor ] = docCreated . map ( Number ) ;
331
+ const host = 'https://nodejs.org' ;
332
+ const versions = [
333
+ { num : '10.x' } ,
334
+ { num : '9.x' } ,
335
+ { num : '8.x' , lts : true } ,
336
+ { num : '7.x' } ,
337
+ { num : '6.x' , lts : true } ,
338
+ { num : '5.x' } ,
339
+ { num : '4.x' , lts : true } ,
340
+ { num : '0.12.x' } ,
341
+ { num : '0.10.x' }
342
+ ] ;
343
+
344
+ const getHref = ( versionNum ) =>
345
+ `${ host } /docs/latest-v${ versionNum } /api/${ filename } .html` ;
346
+
347
+ const wrapInListItem = ( version ) =>
348
+ `<li><a href="${ getHref ( version . num ) } ">${ version . num } ` +
349
+ `${ version . lts ? ' <b>LTS</b>' : '' } </a></li>` ;
350
+
351
+ function isDocInVersion ( version ) {
352
+ const [ versionMajor , versionMinor ] = version . num . split ( '.' ) . map ( Number ) ;
353
+ if ( docCreatedMajor > versionMajor ) return false ;
354
+ if ( docCreatedMajor < versionMajor ) return true ;
355
+ return docCreatedMinor <= versionMinor ;
356
+ }
357
+
358
+ const list = versions . filter ( isDocInVersion ) . map ( wrapInListItem ) . join ( '\n' ) ;
359
+
360
+ return list ? `
361
+ <li class="version-picker">
362
+ <a href="#">View another version <span>▼</span></a>
363
+ <ol class="version-picker">${ list } </ol>
364
+ </li>
365
+ ` : '' ;
460
366
}
0 commit comments