@@ -24,10 +24,121 @@ SOFTWARE.
2424
2525*/
2626
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+ }
2736const fs = require ( 'fs' ) ;
2837const path = require ( 'path' ) ;
29-
38+ const { JSDOM } = require ( 'jsdom' ) ;
3039const [ 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+ }
31142
32143const biMDtoHTML = ( input ) => {
33144 let text = input ;
@@ -120,6 +231,9 @@ function hbuoclpMDtoHTML(text, maxBlockquoteLevel = 4) {
120231 return resultTextArray . join ( '' ) ;
121232}
122233
234+ const link = ( text , link_ , ext = false ) => `<a href="${ link_ } "${ ext ? ' id="ext"' : '' } >${ text } </a>` ;
235+ const span = ( text ) => `<span>${ text } </span>` ;
236+
123237function findMarkdownFiles ( dir ) {
124238 let results = [ ] ;
125239 const list = fs . readdirSync ( dir ) ;
@@ -135,17 +249,122 @@ function findMarkdownFiles(dir) {
135249 return results ;
136250}
137251
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+ }
140324
141325markdownFiles . forEach ( file => {
142- const content = fs . readFileSync ( file , 'utf-8' ) ;
326+ const content = fs . readFileSync ( file , charset ) ;
143327 const fileNameWithoutExt = path . basename ( file , path . extname ( file ) ) ;
144328 const outFilePath = ( ext ) => path . join ( path . dirname ( file ) , `${ fileNameWithoutExt } .${ ext } ` ) ;
145329
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 ) ) ;
147366
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 ) ;
151370} ) ;
0 commit comments