mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	script to build the docs website
This commit is contained in:
		
							
								
								
									
										123
									
								
								src/build_docs_website.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/build_docs_website.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | const fs = require("fs-extra"); | ||||||
|  | const utils = require("./services/utils.js"); | ||||||
|  | const html = require("html"); | ||||||
|  |  | ||||||
|  | const USER_GUIDE_DIR = './docs/user_guide'; | ||||||
|  | const META_PATH = USER_GUIDE_DIR + '/!!!meta.json'; | ||||||
|  | const WEB_TMP_DIR = './tmp/user_guide_web'; | ||||||
|  | fs.copySync(USER_GUIDE_DIR, WEB_TMP_DIR); | ||||||
|  |  | ||||||
|  | const meta = JSON.parse(fs.readFileSync(META_PATH).toString()); | ||||||
|  | const rootNoteMeta = meta.files[0]; | ||||||
|  | const noteIdToMeta = {}; | ||||||
|  | createNoteIdToMetaMapping(rootNoteMeta); | ||||||
|  |  | ||||||
|  | addNavigationAndStyle(rootNoteMeta, WEB_TMP_DIR); | ||||||
|  |  | ||||||
|  | fs.writeFileSync(WEB_TMP_DIR + '/style.css', getCss()); | ||||||
|  |  | ||||||
|  | function getCss() { | ||||||
|  |     return '* { color: red }'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function addNavigationAndStyle(noteMeta, parentDirPath) { | ||||||
|  |     const nav = createNavigation(rootNoteMeta, noteMeta); | ||||||
|  |  | ||||||
|  |     if (noteMeta.dataFileName) { | ||||||
|  |         const filePath = parentDirPath + "/" + noteMeta.dataFileName; | ||||||
|  |  | ||||||
|  |         console.log(`Adding nav to ${filePath}`); | ||||||
|  |  | ||||||
|  |         const content = fs.readFileSync(filePath).toString(); | ||||||
|  |         const depth = noteMeta.notePath.length - 1; | ||||||
|  |         const updatedContent = content | ||||||
|  |             .replaceAll("</head>", `<link rel="stylesheet" href="${"../".repeat(depth)}styles.css">`) | ||||||
|  |             .replaceAll("</body>", nav + "</body>"); | ||||||
|  |         const prettified = html.prettyPrint(updatedContent, {indent_size: 2}); | ||||||
|  |         fs.writeFileSync(filePath, prettified); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for (const childNoteMeta of noteMeta.children || []) { | ||||||
|  |         addNavigationAndStyle(childNoteMeta, parentDirPath + '/' + noteMeta.dirFileName); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function createNavigation(rootMeta, sourceMeta) { | ||||||
|  |     function saveNavigationInner(meta) { | ||||||
|  |         let html = '<li>'; | ||||||
|  |  | ||||||
|  |         const escapedTitle = utils.escapeHtml(`${meta.prefix ? `${meta.prefix} - ` : ''}${meta.title}`); | ||||||
|  |  | ||||||
|  |         if (meta.dataFileName) { | ||||||
|  |             const targetUrl = getTargetUrl(meta.noteId, sourceMeta); | ||||||
|  |  | ||||||
|  |             html += `<a href="${targetUrl}">${escapedTitle}</a>`; | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             html += escapedTitle; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (meta.children && meta.children.length > 0) { | ||||||
|  |             html += '<ul>'; | ||||||
|  |  | ||||||
|  |             for (const child of meta.children) { | ||||||
|  |                 html += saveNavigationInner(child); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             html += '</ul>' | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return `${html}</li>`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return `<nav class="note-tree-nav"><ul>${saveNavigationInner(rootMeta)}</ul></nav>`; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function createNoteIdToMetaMapping(noteMeta) { | ||||||
|  |     noteIdToMeta[noteMeta.noteId] = noteMeta; | ||||||
|  |  | ||||||
|  |     for (const childNoteMeta of noteMeta.children || []) { | ||||||
|  |         createNoteIdToMetaMapping(childNoteMeta); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getTargetUrl(targetNoteId, sourceMeta) { | ||||||
|  |     const targetMeta = noteIdToMeta[targetNoteId]; | ||||||
|  |  | ||||||
|  |     if (!targetMeta) { | ||||||
|  |         throw new Error(`Could not find note meta for noteId '${targetNoteId}'`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const targetPath = targetMeta.notePath.slice(); | ||||||
|  |     const sourcePath = sourceMeta.notePath.slice(); | ||||||
|  |  | ||||||
|  |     // > 1 for edge case that targetPath and sourcePath are exact same (link to itself) | ||||||
|  |     while (targetPath.length > 1 && sourcePath.length > 1 && targetPath[0] === sourcePath[0]) { | ||||||
|  |         targetPath.shift(); | ||||||
|  |         sourcePath.shift(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let url = "../".repeat(sourcePath.length - 1); | ||||||
|  |  | ||||||
|  |     for (let i = 0; i < targetPath.length - 1; i++) { | ||||||
|  |         const meta = noteIdToMeta[targetPath[i]]; | ||||||
|  |  | ||||||
|  |         if (!meta) { | ||||||
|  |             throw new Error(`Cannot resolve note '${targetPath[i]}' from path '${targetPath.toString()}'`); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         url += `${encodeURIComponent(meta.dirFileName)}/`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const targetPathNoteId = targetPath[targetPath.length - 1]; | ||||||
|  |     const meta = noteIdToMeta[targetPathNoteId]; | ||||||
|  |  | ||||||
|  |     if (!meta) { | ||||||
|  |         throw new Error(`Cannot resolve note '${targetPathNoteId}' from path '${targetPath.toString()}'`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // link can target note which is only "folder-note" and as such will not have a file in an export | ||||||
|  |     url += encodeURIComponent(meta.dataFileName || meta.dirFileName); | ||||||
|  |  | ||||||
|  |     return url; | ||||||
|  | } | ||||||
| @@ -254,9 +254,10 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true) | |||||||
|     <link rel="stylesheet" href="${cssUrl}"> |     <link rel="stylesheet" href="${cssUrl}"> | ||||||
|     <base target="_parent"> |     <base target="_parent"> | ||||||
| </head> | </head> | ||||||
| <body class="ck-content"> | <body> | ||||||
|   <h1>${utils.escapeHtml(title)}</h1> |   <h1>${utils.escapeHtml(title)}</h1> | ||||||
| ${content} |    | ||||||
|  |   <div class="ck-content">${content}</div> | ||||||
| </body> | </body> | ||||||
| </html>`; | </html>`; | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -1,5 +1,101 @@ | |||||||
| const sanitizeHtml = require('sanitize-html'); | const sanitizeHtml = require('sanitize-html'); | ||||||
|  |  | ||||||
|  | const fs = require("fs"); | ||||||
|  | const path = require("path"); | ||||||
|  | const html = require("html"); | ||||||
|  |  | ||||||
|  | const TMP_API_DOCS = './tmp/api_docs'; | ||||||
|  | const TMP_FE_DOCS = TMP_API_DOCS + '/frontend_api'; | ||||||
|  | const TMP_BE_DOCS = TMP_API_DOCS + '/backend_api'; | ||||||
|  |  | ||||||
|  | const sourceFiles = getFilesRecursively(TMP_API_DOCS); | ||||||
|  |  | ||||||
|  | for (const sourcePath of sourceFiles) { | ||||||
|  |     const content = fs.readFileSync(sourcePath).toString(); | ||||||
|  |     const transformedContent = transform(content); | ||||||
|  |     const prettifiedContent = html.prettyPrint(transformedContent, {indent_size: 2}); | ||||||
|  |     const filteredContent = prettifiedContent | ||||||
|  |         .replace(/<br \/>Documentation generated by <a href="https:\/\/github.com\/jsdoc\/jsdoc">[^<]+<\/a>/gi, '') | ||||||
|  |         .replace(/JSDoc: (Class|Module): [a-z]+/gi, ''); | ||||||
|  |  | ||||||
|  |     const destPath = sourcePath.replaceAll("tmp", "docs"); | ||||||
|  |  | ||||||
|  |     fs.mkdirSync(path.dirname(destPath), {recursive: true}); | ||||||
|  |     fs.writeFileSync(destPath, filteredContent.trim()); | ||||||
|  |  | ||||||
|  |     console.log(destPath); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const USER_GUIDE_DIR = './docs/user_guide'; | ||||||
|  | const META_PATH = USER_GUIDE_DIR + '/!!!meta.json'; | ||||||
|  |  | ||||||
|  | const meta = JSON.parse(fs.readFileSync(META_PATH).toString()); | ||||||
|  | const rootNoteMeta = meta.files[0]; | ||||||
|  |  | ||||||
|  | const {noteMeta: scriptApiDocsRoot, filePath: scriptApiDocsRootFilePath, notePath: scriptApiDocsRootNotePath} = | ||||||
|  |     findNoteMeta(rootNoteMeta, 'Script API', []); | ||||||
|  | const BE_FILES = ['AbstractBeccaEntity', 'BAttribute', 'BBranch', 'BEtapiToken', 'BNote', 'BNoteRevision', 'BOption', 'BRecentNote', 'module-sql']; | ||||||
|  |  | ||||||
|  | const FE_FILES = ['FNote', 'FAttribute', 'FBranch', 'FNoteComplement']; | ||||||
|  |  | ||||||
|  | scriptApiDocsRoot.dirFileName = scriptApiDocsRoot.dataFileName.substr(0, scriptApiDocsRoot.dataFileName.length - 5); | ||||||
|  | scriptApiDocsRoot.children = getScriptApiMeta(); | ||||||
|  |  | ||||||
|  | fs.writeFileSync(META_PATH, JSON.stringify(meta, null, 2)); | ||||||
|  | const scriptApiDocsRootDir =  USER_GUIDE_DIR + scriptApiDocsRootFilePath.substr(0, scriptApiDocsRootFilePath.length - 5); | ||||||
|  |  | ||||||
|  | fs.mkdirSync(scriptApiDocsRootDir, {recursive: true}); | ||||||
|  | fs.mkdirSync(scriptApiDocsRootDir + '/BackendScriptApi', {recursive: true}); | ||||||
|  | fs.mkdirSync(scriptApiDocsRootDir + '/FrontendScriptApi', {recursive: true}); | ||||||
|  |  | ||||||
|  | const BE_ROOT = scriptApiDocsRootDir + '/BackendScriptApi.html'; | ||||||
|  | const FE_ROOT = scriptApiDocsRootDir + '/FrontendScriptApi.html'; | ||||||
|  |  | ||||||
|  | fs.copyFileSync(TMP_BE_DOCS + '/BackendScriptApi.html', BE_ROOT); | ||||||
|  | fs.copyFileSync(TMP_FE_DOCS + '/FrontendScriptApi.html', FE_ROOT); | ||||||
|  |  | ||||||
|  | for (const file of BE_FILES) { | ||||||
|  |     fs.copyFileSync(TMP_BE_DOCS + '/' + file + '.html', scriptApiDocsRootDir + '/BackendScriptApi/' + file + '.html'); | ||||||
|  | } | ||||||
|  | rewriteLinks(BE_ROOT, BE_FILES, 'BackendScriptApi'); | ||||||
|  |  | ||||||
|  | for (const file of FE_FILES) { | ||||||
|  |     fs.copyFileSync(TMP_FE_DOCS + '/' + file + '.html', scriptApiDocsRootDir + '/FrontendScriptApi/' + file + '.html'); | ||||||
|  | } | ||||||
|  | rewriteLinks(FE_ROOT, FE_FILES, 'FrontendScriptApi'); | ||||||
|  |  | ||||||
|  | fs.rmSync(USER_GUIDE_DIR + '/index.html', {force: true}); | ||||||
|  | fs.rmSync(USER_GUIDE_DIR + '/navigation.html', {force: true}); | ||||||
|  | fs.rmSync(USER_GUIDE_DIR + '/style.css', {force: true}); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function getFilesRecursively(directory) { | ||||||
|  |     const files = []; | ||||||
|  |  | ||||||
|  |     function getFilesRecursivelyInner(directory) { | ||||||
|  |         const filesInDirectory = fs.readdirSync(directory); | ||||||
|  |         for (const file of filesInDirectory) { | ||||||
|  |             const absolute = path.join(directory, file); | ||||||
|  |             if (fs.statSync(absolute).isDirectory()) { | ||||||
|  |                 getFilesRecursivelyInner(absolute); | ||||||
|  |             } else if (file.endsWith('.html')) { | ||||||
|  |                 files.push(absolute); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getFilesRecursivelyInner(directory); | ||||||
|  |  | ||||||
|  |     return files; | ||||||
|  | } | ||||||
|  |  | ||||||
| function transform(content) { | function transform(content) { | ||||||
|     const result = sanitizeHtml(content, { |     const result = sanitizeHtml(content, { | ||||||
|         allowedTags: [ |         allowedTags: [ | ||||||
| @@ -24,7 +120,7 @@ function transform(content) { | |||||||
|         }, |         }, | ||||||
|         allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'data', 'evernote'], |         allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'data', 'evernote'], | ||||||
|         transformTags: { |         transformTags: { | ||||||
|            // 'h5': sanitizeHtml.simpleTransform('strong', {}, false), |             // 'h5': sanitizeHtml.simpleTransform('strong', {}, false), | ||||||
|             'table': sanitizeHtml.simpleTransform('table', {}, false) |             'table': sanitizeHtml.simpleTransform('table', {}, false) | ||||||
|         }, |         }, | ||||||
|     }); |     }); | ||||||
| @@ -41,48 +137,6 @@ function transform(content) { | |||||||
|         ; |         ; | ||||||
| } | } | ||||||
|  |  | ||||||
| const fs = require("fs"); |  | ||||||
| const path = require("path"); |  | ||||||
| const html = require("html"); |  | ||||||
| let sourceFiles = []; |  | ||||||
|  |  | ||||||
| const getFilesRecursively = (directory) => { |  | ||||||
|     const filesInDirectory = fs.readdirSync(directory); |  | ||||||
|     for (const file of filesInDirectory) { |  | ||||||
|         const absolute = path.join(directory, file); |  | ||||||
|         if (fs.statSync(absolute).isDirectory()) { |  | ||||||
|             getFilesRecursively(absolute); |  | ||||||
|         } else if (file.endsWith('.html')) { |  | ||||||
|             sourceFiles.push(absolute); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const TMP_API_DOCS = './tmp/api_docs'; |  | ||||||
| const TMP_FE_DOCS = TMP_API_DOCS + '/frontend_api'; |  | ||||||
| const TMP_BE_DOCS = TMP_API_DOCS + '/backend_api'; |  | ||||||
|  |  | ||||||
| getFilesRecursively(TMP_API_DOCS); |  | ||||||
|  |  | ||||||
| for (const sourcePath of sourceFiles) { |  | ||||||
|     const content = fs.readFileSync(sourcePath).toString(); |  | ||||||
|     const transformedContent = transform(content); |  | ||||||
|     const prettifiedContent = html.prettyPrint(transformedContent, {indent_size: 2}); |  | ||||||
|     const filteredContent = prettifiedContent |  | ||||||
|         .replace(/<br \/>Documentation generated by <a href="https:\/\/github.com\/jsdoc\/jsdoc">[^<]+<\/a>/gi, '') |  | ||||||
|         .replace(/JSDoc: (Class|Module): [a-z]+/gi, ''); |  | ||||||
|  |  | ||||||
|     const destPath = sourcePath.replaceAll("tmp", "docs"); |  | ||||||
|  |  | ||||||
|     fs.mkdirSync(path.dirname(destPath), {recursive: true}); |  | ||||||
|     fs.writeFileSync(destPath, filteredContent.trim()); |  | ||||||
|  |  | ||||||
|     console.log(destPath); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const META_PATH = './docs/user_guide/!!!meta.json'; |  | ||||||
| const meta = JSON.parse(fs.readFileSync(META_PATH).toString()); |  | ||||||
|  |  | ||||||
| function findNoteMeta(noteMeta, name, notePath) { | function findNoteMeta(noteMeta, name, notePath) { | ||||||
|     if (noteMeta.title === name) { |     if (noteMeta.title === name) { | ||||||
|         return { |         return { | ||||||
| @@ -107,28 +161,6 @@ function findNoteMeta(noteMeta, name, notePath) { | |||||||
|     return null; |     return null; | ||||||
| } | } | ||||||
|  |  | ||||||
| const {noteMeta: scriptApiDocsRoot, filePath: scriptApiDocsRootFilePath, notePath: scriptApiDocsRootNotePath} = |  | ||||||
|     findNoteMeta(meta.files[0], 'Script API', ['_scriptApi']); |  | ||||||
|  |  | ||||||
| const BE_FILES = ['AbstractBeccaEntity', 'BAttribute', 'BBranch', 'BEtapiToken', 'BNote', 'BNoteRevision', 'BOption', 'BRecentNote', 'module-sql']; |  | ||||||
| const FE_FILES = ['FNote', 'FAttribute', 'FBranch', 'FNoteComplement']; |  | ||||||
|  |  | ||||||
| scriptApiDocsRoot.children = getScriptApiMeta(); |  | ||||||
|  |  | ||||||
| fs.writeFileSync(META_PATH, JSON.stringify(meta, null, 2)); |  | ||||||
|  |  | ||||||
| const scriptApiDocsRootDir =  './docs/user_guide' + scriptApiDocsRootFilePath.substr(0, scriptApiDocsRootFilePath.length - 5); |  | ||||||
|  |  | ||||||
| fs.mkdirSync(scriptApiDocsRootDir, {recursive: true}); |  | ||||||
| fs.mkdirSync(scriptApiDocsRootDir + '/BackendScriptApi', {recursive: true}); |  | ||||||
| fs.mkdirSync(scriptApiDocsRootDir + '/FrontendScriptApi', {recursive: true}); |  | ||||||
|  |  | ||||||
| const BE_ROOT = scriptApiDocsRootDir + '/BackendScriptApi.html'; |  | ||||||
| const FE_ROOT = scriptApiDocsRootDir + '/FrontendScriptApi.html'; |  | ||||||
|  |  | ||||||
| fs.copyFileSync(TMP_BE_DOCS + '/BackendScriptApi.html', BE_ROOT); |  | ||||||
| fs.copyFileSync(TMP_FE_DOCS + '/FrontendScriptApi.html', FE_ROOT); |  | ||||||
|  |  | ||||||
| function rewriteLinks(rootFilePath, files, dir) { | function rewriteLinks(rootFilePath, files, dir) { | ||||||
|     let content = fs.readFileSync(rootFilePath).toString(); |     let content = fs.readFileSync(rootFilePath).toString(); | ||||||
|  |  | ||||||
| @@ -139,27 +171,27 @@ function rewriteLinks(rootFilePath, files, dir) { | |||||||
|     fs.writeFileSync(rootFilePath, content); |     fs.writeFileSync(rootFilePath, content); | ||||||
| } | } | ||||||
|  |  | ||||||
| for (const file of BE_FILES) { |  | ||||||
|     fs.copyFileSync(TMP_BE_DOCS + '/' + file + '.html', scriptApiDocsRootDir + '/BackendScriptApi/' + file + '.html'); |  | ||||||
| } |  | ||||||
| rewriteLinks(BE_ROOT, BE_FILES, 'BackendScriptApi'); |  | ||||||
|  |  | ||||||
| for (const file of FE_FILES) { |  | ||||||
|     fs.copyFileSync(TMP_FE_DOCS + '/' + file + '.html', scriptApiDocsRootDir + '/FrontendScriptApi/' + file + '.html'); |  | ||||||
| } |  | ||||||
| rewriteLinks(FE_ROOT, FE_FILES, 'FrontendScriptApi'); |  | ||||||
|  |  | ||||||
| function createChildren(files, notePath) { | function createChildren(files, notePath) { | ||||||
|     let positionCounter = 0; |     let positionCounter = 0; | ||||||
|  |  | ||||||
|     const camelCase = name => name.charAt(0).toLowerCase() + name.substr(1); |     const camelCase = name => { | ||||||
|  |         if (name === 'module-sql') { | ||||||
|  |             return 'moduleSql'; | ||||||
|  |         } else if (/[^a-z]+/i.test(name)) { | ||||||
|  |             throw new Error(`Bad name '${name}'`); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return name.charAt(0).toLowerCase() + name.substr(1); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     return files.map(file => { |     return files.map(file => { | ||||||
|         positionCounter += 10; |         positionCounter += 10; | ||||||
|  |  | ||||||
|  |         const noteId = "_" + camelCase(file); | ||||||
|  |  | ||||||
|         return { |         return { | ||||||
|             "isClone": false, |             "isClone": false, | ||||||
|             "noteId": "_file", |             "noteId": noteId, | ||||||
|             "notePath": [ |             "notePath": [ | ||||||
|                 ...notePath, |                 ...notePath, | ||||||
|                 '_' + camelCase(file) |                 '_' + camelCase(file) | ||||||
| @@ -195,6 +227,7 @@ function getScriptApiMeta() { | |||||||
|             "attributes": [], |             "attributes": [], | ||||||
|             "format": "html", |             "format": "html", | ||||||
|             "dataFileName": "FrontendScriptApi.html", |             "dataFileName": "FrontendScriptApi.html", | ||||||
|  |             "dirFileName": "FrontendScriptApi", | ||||||
|             "children": createChildren(FE_FILES, [ |             "children": createChildren(FE_FILES, [ | ||||||
|                 ...scriptApiDocsRootNotePath, |                 ...scriptApiDocsRootNotePath, | ||||||
|                 "_frontendApi" |                 "_frontendApi" | ||||||
| @@ -216,6 +249,7 @@ function getScriptApiMeta() { | |||||||
|             "attributes": [], |             "attributes": [], | ||||||
|             "format": "html", |             "format": "html", | ||||||
|             "dataFileName": "BackendScriptApi.html", |             "dataFileName": "BackendScriptApi.html", | ||||||
|  |             "dirFileName": "BackendScriptApi", | ||||||
|             "children": createChildren(BE_FILES, [ |             "children": createChildren(BE_FILES, [ | ||||||
|                 ...scriptApiDocsRootNotePath, |                 ...scriptApiDocsRootNotePath, | ||||||
|                 "_backendApi" |                 "_backendApi" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user