From d7b071978860472aa0ffa9f787a89811870f56b2 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 12 Jan 2023 16:37:30 +0100 Subject: [PATCH] script to build the docs website --- src/build_docs_website.js | 123 ++++++++++++++++++++++++ src/services/export/zip.js | 5 +- src/transform_api_docs.js | 188 ++++++++++++++++++++++--------------- 3 files changed, 237 insertions(+), 79 deletions(-) create mode 100644 src/build_docs_website.js diff --git a/src/build_docs_website.js b/src/build_docs_website.js new file mode 100644 index 000000000..9e3e6a6c5 --- /dev/null +++ b/src/build_docs_website.js @@ -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("", ``) + .replaceAll("", nav + ""); + 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 = '
  • '; + + const escapedTitle = utils.escapeHtml(`${meta.prefix ? `${meta.prefix} - ` : ''}${meta.title}`); + + if (meta.dataFileName) { + const targetUrl = getTargetUrl(meta.noteId, sourceMeta); + + html += `${escapedTitle}`; + } + else { + html += escapedTitle; + } + + if (meta.children && meta.children.length > 0) { + html += '' + } + + return `${html}
  • `; + } + + return ``; +} + +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; +} diff --git a/src/services/export/zip.js b/src/services/export/zip.js index 75429909b..8bc7e6dc6 100644 --- a/src/services/export/zip.js +++ b/src/services/export/zip.js @@ -254,9 +254,10 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true) - +

    ${utils.escapeHtml(title)}

    -${content} + +
    ${content}
    `; } diff --git a/src/transform_api_docs.js b/src/transform_api_docs.js index 836fbf5d4..8212f891b 100644 --- a/src/transform_api_docs.js +++ b/src/transform_api_docs.js @@ -1,5 +1,101 @@ 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(/
    Documentation generated by [^<]+<\/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) { const result = sanitizeHtml(content, { allowedTags: [ @@ -24,7 +120,7 @@ function transform(content) { }, allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'data', 'evernote'], transformTags: { - // 'h5': sanitizeHtml.simpleTransform('strong', {}, false), + // 'h5': sanitizeHtml.simpleTransform('strong', {}, 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(/
    Documentation generated by
    [^<]+<\/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) { if (noteMeta.title === name) { return { @@ -107,28 +161,6 @@ function findNoteMeta(noteMeta, name, notePath) { 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) { let content = fs.readFileSync(rootFilePath).toString(); @@ -139,27 +171,27 @@ function rewriteLinks(rootFilePath, files, dir) { 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) { 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 => { positionCounter += 10; + const noteId = "_" + camelCase(file); + return { "isClone": false, - "noteId": "_file", + "noteId": noteId, "notePath": [ ...notePath, '_' + camelCase(file) @@ -195,6 +227,7 @@ function getScriptApiMeta() { "attributes": [], "format": "html", "dataFileName": "FrontendScriptApi.html", + "dirFileName": "FrontendScriptApi", "children": createChildren(FE_FILES, [ ...scriptApiDocsRootNotePath, "_frontendApi" @@ -216,6 +249,7 @@ function getScriptApiMeta() { "attributes": [], "format": "html", "dataFileName": "BackendScriptApi.html", + "dirFileName": "BackendScriptApi", "children": createChildren(BE_FILES, [ ...scriptApiDocsRootNotePath, "_backendApi"