@@ -24,10 +24,121 @@ SOFTWARE.
24
24
25
25
*/
26
26
27
+ const template = {
28
+ "charset" : "utf-8" ,
29
+ "title" : "Documentation" ,
30
+ "footer" : `Made with ${ link ( '_just' , 'https://just.is-a.dev/' ) } .` ,
31
+ "viewport" : "width=device-width, initial-scale=1.0" ,
32
+ "twitter" : "summary_large_image" ,
33
+ "lang" : "en" ,
34
+ "headerTagIDStart" : "hdr"
35
+ }
27
36
const fs = require ( 'fs' ) ;
28
37
const path = require ( 'path' ) ;
29
-
38
+ const { JSDOM } = require ( 'jsdom' ) ;
30
39
const [ HTML , CSS , JS ] = process . argv . slice ( 2 ) ;
40
+ const config = JSON . parse ( fs . readFileSync ( 'just.config.json' , template . charset ) ) ;
41
+ const docsConfig = config . docs_config ;
42
+
43
+ const charset = docsConfig ? docsConfig . charset || template . charset : template . charset ;
44
+
45
+ const rootDirA = './' ;
46
+ const extensions = [ '.md' , '.mdx' , '.html' ] ;
47
+
48
+ function getFiles ( dir ) {
49
+ let results = [ ] ;
50
+ const list = fs . readdirSync ( dir ) ;
51
+ list . forEach ( file => {
52
+ const filePath = path . join ( dir , file ) ;
53
+ const stat = fs . statSync ( filePath ) ;
54
+ if ( stat && stat . isDirectory ( ) ) {
55
+ results = results . concat ( getFiles ( filePath ) ) ;
56
+ } else if ( extensions . includes ( path . extname ( file ) ) ) {
57
+ results . push ( filePath ) ;
58
+ }
59
+ } ) ;
60
+ return results ;
61
+ }
62
+
63
+ function getTitleFromHtml ( filePath ) {
64
+ const content = fs . readFileSync ( filePath , charset ) ;
65
+ const dom = new JSDOM ( content ) ;
66
+ const title = dom . window . document . querySelector ( 'title' ) ;
67
+ return title ? title . textContent : null ;
68
+ }
69
+
70
+ function getTitleFromMd ( filePath ) {
71
+ const content = fs . readFileSync ( filePath , charset ) . split ( '\n' ) ;
72
+ if ( content [ 0 ] . startsWith ( '_just: title: ' ) ) {
73
+ return content [ 0 ] . replace ( '_just: title: ' , '' ) . trim ( ) ;
74
+ }
75
+ return null ;
76
+ }
77
+
78
+ function getPageList ( ) {
79
+ const files = getFiles ( rootDirA ) ;
80
+ const pages = [ ] ;
81
+
82
+ files . forEach ( file => {
83
+ const extname = path . extname ( file ) ;
84
+ let title ;
85
+ let pagePath = file . replace ( rootDirA , '' ) . replace ( extname , '' ) ;
86
+
87
+ if ( pagePath . endsWith ( '/index' ) ) {
88
+ pagePath = pagePath . split ( '' ) . reverse ( ) . join ( '' ) . replace ( 'index' . split ( '' ) . reverse ( ) . join ( '' ) , '' ) . split ( '' ) . reverse ( ) . join ( '' ) ;
89
+ title = 'Home' ;
90
+ } else {
91
+ title = path . basename ( pagePath ) ;
92
+ }
93
+
94
+ if ( extname === '.html' ) {
95
+ const htmlTitle = getTitleFromHtml ( file ) ;
96
+ if ( htmlTitle ) title = htmlTitle ;
97
+ } else if ( extname === '.md' || extname === '.mdx' ) {
98
+ const mdTitle = getTitleFromMd ( file ) ;
99
+ if ( mdTitle ) title = mdTitle ;
100
+ }
101
+
102
+ pages . push ( { path : pagePath , title } ) ;
103
+ } ) ;
104
+
105
+ return pages ;
106
+ }
107
+ function addFolderToPageList ( pageList ) {
108
+ return pageList . map ( page => {
109
+ const folderNameArray = page . path . split ( '/' ) . filter ( Boolean ) ;
110
+ const folderName = folderNameArray . length > 1 ? folderNameArray [ folderNameArray . length - 2 ] : null ;
111
+ return { ...page , folder : folderName } ;
112
+ } ) ;
113
+ }
114
+ const pageList = getPageList ( ) ;
115
+
116
+ function generateListItems ( PageList ) {
117
+ const folderMap = { } ;
118
+
119
+ PageList . forEach ( page => {
120
+ const folder = page . folder || '' ;
121
+ if ( ! folderMap [ folder ] ) {
122
+ folderMap [ folder ] = [ ] ;
123
+ }
124
+ folderMap [ folder ] . push ( page ) ;
125
+ } ) ;
126
+
127
+ let listItemsHtml = '' ;
128
+
129
+ for ( const [ folderName , pages ] of Object . entries ( folderMap ) ) {
130
+ listItemsHtml += `${ folderName != '' ? `<li>
131
+ <span><strong>${ folderName } </strong></span>
132
+ <ul>` : '<li><ul>' } `;
133
+ pages . forEach ( page => {
134
+ listItemsHtml += `<li><a href="${ page . path } "><span>${ page . title } </span></a></li>` ;
135
+ } ) ;
136
+ listItemsHtml += ` </ul>
137
+ </li>` ;
138
+ }
139
+
140
+ return listItemsHtml ;
141
+ }
31
142
32
143
const biMDtoHTML = ( input ) => {
33
144
let text = input ;
@@ -120,6 +231,9 @@ function hbuoclpMDtoHTML(text, maxBlockquoteLevel = 4) {
120
231
return resultTextArray . join ( '' ) ;
121
232
}
122
233
234
+ const link = ( text , link_ , ext = false ) => `<a href="${ link_ } "${ ext ? ' id="ext"' : '' } >${ text } </a>` ;
235
+ const span = ( text ) => `<span>${ text } </span>` ;
236
+
123
237
function findMarkdownFiles ( dir ) {
124
238
let results = [ ] ;
125
239
const list = fs . readdirSync ( dir ) ;
@@ -135,17 +249,122 @@ function findMarkdownFiles(dir) {
135
249
return results ;
136
250
}
137
251
138
- const rootDir = process . cwd ( ) ;
139
- const markdownFiles = findMarkdownFiles ( rootDir ) ;
252
+ const rootDirB = process . cwd ( ) ;
253
+ const markdownFiles = findMarkdownFiles ( rootDirB ) ;
254
+
255
+ const title = docsConfig ? docsConfig . title || template . title : template . title ;
256
+ const metatitle = docsConfig ? docsConfig . metatitle || title : title ;
257
+ const ogtitle = docsConfig && docsConfig . og ? docsConfig . og . title || metatitle : metatitle ;
258
+ const description = docsConfig ? docsConfig . description || undefined : undefined ;
259
+ const ogdescription = docsConfig && docsConfig . og ? docsConfig . og . description || description : description ;
260
+ const viewport = docsConfig ? docsConfig . viewport || template . viewport : template . viewport ;
261
+ const twitter = docsConfig && docsConfig . twitter ? docsConfig . twitter . card || template . twitter : template . twitter ;
262
+ const metaKeywords = docsConfig ? docsConfig . keywords || undefined : undefined ;
263
+ const lang = docsConfig ? docsConfig . htmlLang || template . lang : template . lang ;
264
+ const yandexVerification = docsConfig ? docsConfig . yandex || undefined : undefined ;
265
+ const googleAnalytics = docsConfig ? docsConfig . googleAnalytics || undefined : undefined ;
266
+ const googleVerification = docsConfig ? docsConfig . google || undefined : undefined ;
267
+ const logoPath = docsConfig ? docsConfig . logo || undefined : undefined ;
268
+
269
+ const insertHTMLinHead = docsConfig ? docsConfig . insertInHTMLHead || '' : '' ;
270
+
271
+ const keywords = metaKeywords ? `<meta name="keywords" content="${ metaKeywords } "/>` : '' ;
272
+ const desc = description ? `<meta name="description" content="${ description } "/>` : '' ;
273
+ const ogdesc = ogdescription ? `<meta property="og:description" content="${ ogdescription } "/>` : '' ;
274
+ const ogtitl = ogtitle ? `<meta property="og:title" content="${ ogtitle } "/>` : '' ;
275
+ const logo = logoPath ? `<img src="${ logoPath } " width="35px" height="auto" alt="Logo">` : '' ;
276
+ const name = docsConfig && docsConfig . title ? span ( title ) : logoPath ? '' : span ( title ) ;
277
+ const htmlLang = lang ? ` lang="${ `${ lang } ` . toLowerCase ( ) } "` : '' ;
278
+ const htmlhead = ( ) => {
279
+ let output = `
280
+ ${ keywords }
281
+ ${ desc }
282
+ ${ ogtitl }
283
+ ${ ogdesc }
284
+ <meta property="og:type" content="website"/>` ;
285
+ if ( twitter ) {
286
+ output += `<meta property="twitter:card" content="${ twitter } "/>`
287
+ }
288
+ if ( yandexVerification ) {
289
+ output += `\n<meta name="yandex-verification" content="${ yandexVerification } "/>` ;
290
+ }
291
+ if ( googleVerification ) {
292
+ output += `\n<meta name="google-site-verification" content="${ googleVerification } " />` ;
293
+ }
294
+ if ( googleAnalytics ) {
295
+ output += `\n<script async src="https://www.googletagmanager.com/gtag/js?id=${ googleAnalytics } "></script>
296
+ <script>
297
+ window.dataLayer = window.dataLayer || [];
298
+ function gtag() {
299
+ dataLayer.push(arguments);
300
+ }
301
+ gtag('js', new Date());
302
+ gtag('config', '${ googleAnalytics } ');
303
+ </script>`
304
+ }
305
+ return output ;
306
+ }
307
+
308
+ filterText = ( text ) => text
309
+ . replaceAll ( '_' , `&#${ '_' . charCodeAt ( 0 ) } ` )
310
+ . replaceAll ( '<script>' , `&#${ '<' . charCodeAt ( 0 ) } script&#${ '>' . charCodeAt ( 0 ) } ` )
311
+ . replaceAll ( '</script>' , `&#${ '<' . charCodeAt ( 0 ) } &#${ '/' . charCodeAt ( 0 ) } script&#${ '>' . charCodeAt ( 0 ) } ` ) ;
312
+ function makeJSDOM ( data ) {
313
+ return `<!DOCTYPE html>
314
+ <html>
315
+ <head>
316
+ <meta charset="${ charset } ">
317
+ <title>hello, world!</title>
318
+ </head>
319
+ <body>
320
+ ${ data }
321
+ </body>
322
+ </html>` ;
323
+ }
140
324
141
325
markdownFiles . forEach ( file => {
142
- const content = fs . readFileSync ( file , 'utf-8' ) ;
326
+ const content = fs . readFileSync ( file , charset ) ;
143
327
const fileNameWithoutExt = path . basename ( file , path . extname ( file ) ) ;
144
328
const outFilePath = ( ext ) => path . join ( path . dirname ( file ) , `${ fileNameWithoutExt } .${ ext } ` ) ;
145
329
146
- const toHTML = hbuoclpMDtoHTML ( content ) ;
330
+ const toHTML = hbuoclpMDtoHTML ( content ) . replace ( / < h 1 > ( .* ?) < \/ h 1 > / g, ( match , p1 ) => {
331
+ return `<h1 id="${ headerTagIDStart } ${ index ++ } ">${ p1 } </h1>` ;
332
+ } ) . replace ( / < h 2 > ( .* ?) < \/ h 2 > / g, ( match , p1 ) => {
333
+ return `<h2 id="${ headerTagIDStart } ${ index ++ } ">${ p1 } </h2>` ;
334
+ } ) . replace ( / < h 3 > ( .* ?) < \/ h 3 > / g, ( match , p1 ) => {
335
+ return `<h3 id="${ headerTagIDStart } ${ index ++ } ">${ p1 } </h3>` ;
336
+ } ) ;
337
+ const dom = new JSDOM ( makeJSDOM ( toHTML ) ) ;
338
+ const document = dom . window . document ;
339
+ const h1 = Array . from ( document . querySelectorAll ( 'h1' ) ) . map ( h => [ h . textContent , h . id ] ) ;
340
+ const hT = Array . from ( document . querySelectorAll ( 'h2, h3' ) ) . map ( h => [ h . textContent , h . id ] ) ;
341
+ const contents = [
342
+ ...h1 . map ( item => ( { ...item , first : true } ) ) ,
343
+ ...hT . map ( item => ( { ...item , first : false } ) )
344
+ ] ;
345
+ let pageHeaders = '' ;
346
+ for ( const [ text , id , first ] of Object . entries ( contents ) ) {
347
+ pageHeaders += `<li${ first ? ' class="secondary"' : '' } >
348
+ <a href="#${ id } ">
349
+ <span>${ text } </span>
350
+ </a>
351
+ </li>` ;
352
+ }
353
+
354
+ const pages = generateListItems ( addFolderToPageList ( pageList ) ) ;
355
+ let outHTML = HTML
356
+ . replace ( '<html>' , `<html lang="${ htmlLang } ">` )
357
+ . replace ( 'REPLACE_CHARSET' , charset )
358
+ . replace ( 'REPLACE_VIEWPORT' , viewport )
359
+ . replace ( 'REPLACE_TITLE' , metatitle )
360
+ . replace ( 'REPLACE_DATA' , htmlhead ( ) )
361
+ . replace ( 'REPLACE_CUSTOM' , insertHTMLinHead )
362
+ . replace ( 'REPLACE_LOGO' , logo )
363
+ . replace ( 'REPLACE_NAME' , filterText ( name ) )
364
+ . replace ( 'REPLACE_PAGES' , filterText ( pages ) )
365
+ . replace ( 'REPLACE_CONTENTS' , filterText ( pageHeaders ) ) ;
147
366
148
- fs . writeFileSync ( outFilePath ( 'html' ) , HTML . replace ( 'REPLACE_CONTENT' , toHTML ) , 'utf-8' ) ;
149
- fs . writeFileSync ( outFilePath ( 'css' ) , CSS , 'utf-8' ) ;
150
- fs . writeFileSync ( outFilePath ( 'js' ) , JS , 'utf-8' ) ;
367
+ fs . writeFileSync ( outFilePath ( 'html' ) , outHTML . replace ( 'REPLACE_CONTENT' , toHTML ) , charset ) ;
368
+ fs . writeFileSync ( outFilePath ( 'css' ) , CSS , template . charset ) ;
369
+ fs . writeFileSync ( outFilePath ( 'js' ) , JS , template . charset ) ;
151
370
} ) ;
0 commit comments