From bfc23e3d58b3550201fd5686e0f0c22d3f66f538 Mon Sep 17 00:00:00 2001 From: Maxim Karpov Date: Fri, 17 May 2024 12:01:26 +0300 Subject: [PATCH] fix: add support for sub toc files --- package-lock.json | 8 +-- package.json | 2 +- src/models.ts | 2 + src/resolvers/md2html.ts | 50 ++++++++++++-- src/services/metadata.ts | 6 +- src/services/tocs.ts | 69 +++++++++++++++++++ src/steps/processPages.ts | 16 ++--- src/steps/processServiceFiles.ts | 7 +- src/utils/toc.ts | 13 ++-- .../__snapshots__/include-toc.test.ts.snap | 5 ++ .../load-custom-resources.spec.ts.snap | 17 ++--- tests/e2e/__snapshots__/metadata.spec.ts.snap | 7 +- tests/e2e/__snapshots__/rtl.spec.ts.snap | 12 ++-- 13 files changed, 162 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index f56a919d..a4b9fbaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@diplodoc/mermaid-extension": "^1.2.1", "@diplodoc/openapi-extension": "^2.1.0", "@diplodoc/prettier-config": "^2.0.0", - "@diplodoc/transform": "^4.15.0", + "@diplodoc/transform": "^4.16.1", "@diplodoc/tsconfig": "^1.0.2", "@octokit/core": "4.2.4", "@types/async": "^3.2.15", @@ -1820,9 +1820,9 @@ } }, "node_modules/@diplodoc/transform": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@diplodoc/transform/-/transform-4.15.0.tgz", - "integrity": "sha512-GMftCJgzBbBrrtt/HaBdmxlFf1Mv62DhrgN288fnF0chtWBrMhctmNbpke6Bf19uSq9d6pyInE3FF2CFm8zW1A==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@diplodoc/transform/-/transform-4.16.1.tgz", + "integrity": "sha512-ZpNI/JgxSNj39ahTdOSnUyWeDuP+5hN+oO2PKSmXGWgonAlreohi3GgVb1tTswiBGQPIyVWdg2syvwHwJUxXxg==", "dependencies": { "@diplodoc/tabs-extension": "^2.1.0", "chalk": "^4.1.2", diff --git a/package.json b/package.json index 1aa9b6c9..37928bed 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@diplodoc/mermaid-extension": "^1.2.1", "@diplodoc/openapi-extension": "^2.1.0", "@diplodoc/prettier-config": "^2.0.0", - "@diplodoc/transform": "^4.15.0", + "@diplodoc/transform": "^4.16.1", "@diplodoc/tsconfig": "^1.0.2", "@octokit/core": "4.2.4", "@types/async": "^3.2.15", diff --git a/src/models.ts b/src/models.ts index 5a8020ad..efee4e82 100644 --- a/src/models.ts +++ b/src/models.ts @@ -96,12 +96,14 @@ export interface YfmToc extends Filter { items: YfmToc[]; stage?: Stage; base?: string; + deepBase?: number; title?: TextItems; include?: YfmTocInclude; id?: string; singlePage?: boolean; hidden?: boolean; label?: YfmTocLabel[] | YfmTocLabel; + root?: YfmToc; } export interface YfmTocInclude { diff --git a/src/resolvers/md2html.ts b/src/resolvers/md2html.ts index 19d229ac..1c59ab18 100644 --- a/src/resolvers/md2html.ts +++ b/src/resolvers/md2html.ts @@ -8,7 +8,7 @@ import transform, {Output} from '@diplodoc/transform'; import liquid from '@diplodoc/transform/lib/liquid'; import log from '@diplodoc/transform/lib/log'; import {MarkdownItPluginCb} from '@diplodoc/transform/lib/plugins/typings'; -import {getPublicPath} from '@diplodoc/transform/lib/utilsFS'; +import {getPublicPath, isFileExists} from '@diplodoc/transform/lib/utilsFS'; import yaml from 'js-yaml'; import {Lang, PROCESSING_FINISHED} from '../constants'; @@ -79,7 +79,7 @@ const getFileProps = async (options: ResolverOptions) => { const pathToDir: string = dirname(inputPath); const toc: YfmToc | null = TocService.getForPath(inputPath) || null; - const tocBase: string = toc?.base ?? ''; + const tocBase: string = toc?.root?.base || toc?.base || ''; const pathToFileDir: string = pathToDir === tocBase ? '' : pathToDir.replace(`${tocBase}${sep}`, ''); @@ -92,6 +92,8 @@ const getFileProps = async (options: ResolverOptions) => { const lang = tocLang || configLang || configLangs?.[0] || Lang.RU; const langs = configLangs?.length ? configLangs : [lang]; + const pathname = join(pathToFileDir, basename(outputPath)); + const props = { data: { leading: inputPath.endsWith('.yaml'), @@ -99,7 +101,7 @@ const getFileProps = async (options: ResolverOptions) => { ...meta, }, router: { - pathname: join(pathToFileDir, basename(outputPath)), + pathname, }, lang, langs, @@ -119,6 +121,35 @@ export async function resolveMd2HTML(options: ResolverOptions): Promise - link.href ? {...link, href: link.href.replace(/.md$/gmu, '.html')} : link, - ); + const links = data?.links?.map((link) => { + if (link.href) { + const href = getHref(transformOptions.path, link.href); + return { + ...link, + href, + }; + } + return link; + }); if (links) { data.links = links; diff --git a/src/services/metadata.ts b/src/services/metadata.ts index 1e7292dd..24bcb424 100644 --- a/src/services/metadata.ts +++ b/src/services/metadata.ts @@ -15,7 +15,6 @@ import { import {isObject} from './utils'; import {сarriage} from '../utils'; import {REGEXP_AUTHOR, metadataBorder} from '../constants'; -import {sep} from 'path'; import {TocService} from './index'; async function getContentWithUpdatedMetadata( @@ -329,8 +328,7 @@ function getSystemVarsMetadataString(systemVars: object) { function getAssetsPublicPath(filePath: string) { const toc: YfmToc | null = TocService.getForPath(filePath) || null; - const basePath = toc?.base?.split(sep)?.filter((str) => str !== '.') || []; - const deepBase = basePath.length; + const deepBase = toc?.root?.deepBase || toc?.deepBase || 0; const deepBasePath = deepBase > 0 ? Array(deepBase).fill('../').join('') : './'; /* Relative path from folder of .md file to root of user' output folder */ @@ -340,7 +338,7 @@ function getAssetsPublicPath(filePath: string) { function getAssetsRootPath(filePath: string) { const toc: YfmToc | null = TocService.getForPath(filePath) || null; - return toc?.base; + return toc?.root?.base || toc?.base; } export { diff --git a/src/services/tocs.ts b/src/services/tocs.ts index f66764d5..1f73b967 100644 --- a/src/services/tocs.ts +++ b/src/services/tocs.ts @@ -28,6 +28,26 @@ let navigationPaths: TocServiceData['navigationPaths'] = []; const includedTocPaths: TocServiceData['includedTocPaths'] = new Set(); const tocFileCopyMap = new Map(); +async function init(tocFilePaths: string[]) { + for (const path of tocFilePaths) { + logger.proc(path); + + await add(path); + } + + for (const path of tocFilePaths) { + logger.proc(path); + + await addRoot(path); + } + + for (const path of tocFilePaths) { + logger.proc(path); + + await addNavigation(path); + } +} + async function add(path: string) { const { input: inputFolderPath, @@ -83,6 +103,7 @@ async function add(path: string) { /* Store path to toc file to handle relative paths in navigation */ parsedToc.base = pathToDir; + parsedToc.deepBase = pathToDir?.split(sep)?.filter((str) => str !== '.')?.length || 0; if (outputFormat === 'md') { /* Should copy resolved and filtered toc to output folder */ @@ -91,7 +112,37 @@ async function add(path: string) { shell.mkdir('-p', dirname(outputPath)); writeFileSync(outputPath, outputToc); } +} + +// To collect root toc.yaml we need to move from root into deep +async function addRoot(path: string) { + const dirPath = dirname(path).split(sep); + + const currentToc = getForPath(path); + + if (!currentToc) { + return; + } + + for (let index = 1; index < dirPath.length; index++) { + const dir = dirPath.slice(0, index); + const rootToc = getForPath(join(dir.join(sep), 'toc.yaml')); + if (rootToc) { + currentToc.root = rootToc; + return; + } + } +} + +// To collect root toc.yaml we need to move from root into deep +async function addNavigation(path: string) { + const parsedToc = getForPath(path); + if (!parsedToc) { + return; + } + + const pathToDir = dirname(path); prepareNavigationPaths(parsedToc, pathToDir); } @@ -126,6 +177,21 @@ function getForPath(path: string): YfmToc | undefined { return storage.get(path); } +function getDeepForPath(path: string): { + deepBase: number; + deep: number; +} { + const toc: YfmToc | null = getForPath(path) || null; + + const deepBase = toc?.root?.deepBase || toc?.deepBase || 0; + const deep = path.split(sep).length - 1 - deepBase; + + return { + deepBase, + deep, + }; +} + function getNavigationPaths(): string[] { return [...navigationPaths]; } @@ -421,8 +487,11 @@ function getAllTocs() { } export default { + init, add, + process, getForPath, + getDeepForPath, getNavigationPaths, getTocDir, getIncludedTocPaths, diff --git a/src/steps/processPages.ts b/src/steps/processPages.ts index c7fd4bd9..a6ba56d9 100644 --- a/src/steps/processPages.ts +++ b/src/steps/processPages.ts @@ -1,5 +1,5 @@ import type {DocInnerProps} from '@diplodoc/client'; -import {basename, dirname, extname, join, relative, resolve, sep} from 'path'; +import {basename, dirname, extname, join, relative, resolve} from 'path'; import {existsSync, readFileSync, writeFileSync} from 'fs'; import log from '@diplodoc/transform/lib/log'; import {asyncify, mapLimit} from 'async'; @@ -171,13 +171,13 @@ async function saveSinglePages() { langs, }; - const basePath = toc?.base?.split(sep)?.filter((str) => str !== '.') || []; - const deepBase = basePath.length; - // Save the full single page for viewing locally const singlePageFn = join(tocDir, SINGLE_PAGE_FILENAME); const singlePageDataFn = join(tocDir, SINGLE_PAGE_DATA_FILENAME); - const singlePageContent = generateStaticMarkup(pageData, deepBase); + const singlePageContent = generateStaticMarkup( + pageData, + toc?.root?.deepBase || toc?.deepBase || 0, + ); writeFileSync(singlePageFn, singlePageContent); writeFileSync(singlePageDataFn, JSON.stringify(pageData)); @@ -358,11 +358,7 @@ async function processingFileToHtml( metaDataOptions: MetaDataOptions, ): Promise { const {outputBundlePath, filename, fileExtension, outputPath, pathToFile} = path; - const toc: YfmToc | null = TocService.getForPath(pathToFile) || null; - - const basePath = toc?.base?.split(sep)?.filter((str) => str !== '.') || []; - const deepBase = basePath.length; - const deep = pathToFile.split(sep).length - 1 - deepBase; + const {deepBase, deep} = TocService.getDeepForPath(pathToFile); return resolveMd2HTML({ inputPath: pathToFile, diff --git a/src/steps/processServiceFiles.ts b/src/steps/processServiceFiles.ts index d5bc321a..a9aa7fcc 100644 --- a/src/steps/processServiceFiles.ts +++ b/src/steps/processServiceFiles.ts @@ -84,12 +84,7 @@ async function preparingTocFiles( ): Promise { try { const tocFilePaths = getFilePathsByGlobals(['**/toc.yaml']); - - for (const path of tocFilePaths) { - logger.proc(path); - - await TocService.add(path); - } + await TocService.init(tocFilePaths); } catch (error) { log.error(`Preparing toc.yaml files failed. Error: ${error}`); throw error; diff --git a/src/utils/toc.ts b/src/utils/toc.ts index 56758b10..e973c1e4 100644 --- a/src/utils/toc.ts +++ b/src/utils/toc.ts @@ -1,4 +1,4 @@ -import {basename, dirname, extname, format, join} from 'path'; +import {basename, dirname, extname, format, join, relative} from 'path'; import {YfmToc} from '../models'; import {filterFiles} from '../services/utils'; @@ -39,14 +39,19 @@ export function transformToc(toc: YfmToc | null): YfmToc | null { } if (href && !isExternalHref(href)) { - const fileExtension: string = extname(href); - const filename: string = basename(href, fileExtension); + const relativeHref = join( + relative(toc.root?.base || toc.base || '', toc.base || ''), + href, + ); + + const fileExtension: string = extname(relativeHref); + const filename: string = basename(relativeHref, fileExtension); const transformedFilename: string = format({ name: filename, ext: '.html', }); - navigationItem.href = join(dirname(href), transformedFilename); + navigationItem.href = join(dirname(relativeHref), transformedFilename); } } diff --git a/tests/e2e/__snapshots__/include-toc.test.ts.snap b/tests/e2e/__snapshots__/include-toc.test.ts.snap index f4a6da38..986277f4 100644 --- a/tests/e2e/__snapshots__/include-toc.test.ts.snap +++ b/tests/e2e/__snapshots__/include-toc.test.ts.snap @@ -25,6 +25,7 @@ items: - name: Article1 href: article1.md base: product1 +deepBase: 1 " `; @@ -107,6 +108,7 @@ items: - name: Article1 href: overlay3/article1.md base: product2 +deepBase: 1 " `; @@ -140,6 +142,7 @@ exports[`Include toc Toc is included in link mode 5`] = ` - name: A1 href: folder1/folder2/a1.md base: . +deepBase: 0 " `; @@ -254,6 +257,7 @@ exports[`Include toc Toc is included inline, not as a new section 12`] = ` - name: NameX href: fileX.md base: . +deepBase: 0 " `; @@ -284,5 +288,6 @@ items: - name: A1 href: a1.md base: . +deepBase: 0 " `; diff --git a/tests/e2e/__snapshots__/load-custom-resources.spec.ts.snap b/tests/e2e/__snapshots__/load-custom-resources.spec.ts.snap index 7a08b6af..60b3cb7f 100644 --- a/tests/e2e/__snapshots__/load-custom-resources.spec.ts.snap +++ b/tests/e2e/__snapshots__/load-custom-resources.spec.ts.snap @@ -48,7 +48,7 @@ exports[`Allow load custom resources md2html single page with custom resources 2 @@ -169,7 +169,7 @@ exports[`Allow load custom resources md2html single page with custom resources 4 @@ -234,7 +234,7 @@ exports[`Allow load custom resources md2html single page with custom resources 5

/n

Lorem -

/n","headings":[],"meta":{"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"]},"toc":{"title":"Documentation","href":"index.yaml","items":[{"name":"Documentation","href":"#_page","id":"Documentation-RANDOM"},{"name":"Config","href":"#_project_config","id":"Config-RANDOM"}],"base":".","singlePage":true}},"router":{"pathname":"single-page.html"},"lang":"ru","langs":["ru"]}; +

/n","headings":[],"meta":{"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"]},"toc":{"title":"Documentation","href":"index.yaml","items":[{"name":"Documentation","href":"#_page","id":"Documentation-RANDOM"},{"name":"Config","href":"#_project_config","id":"Config-RANDOM"}],"base":".","deepBase":0,"singlePage":true}},"router":{"pathname":"single-page.html"},"lang":"ru","langs":["ru"]}; @@ -423,7 +423,7 @@ exports[`Allow load custom resources md2html with custom resources 4`] = ` @@ -507,5 +507,6 @@ items: - name: Config href: project/config.md base: . +deepBase: 0 " `; diff --git a/tests/e2e/__snapshots__/metadata.spec.ts.snap b/tests/e2e/__snapshots__/metadata.spec.ts.snap index 0f1781d4..22e5d45f 100644 --- a/tests/e2e/__snapshots__/metadata.spec.ts.snap +++ b/tests/e2e/__snapshots__/metadata.spec.ts.snap @@ -47,7 +47,7 @@ exports[`Allow load custom resources md2html with metadata 2`] = ` @@ -169,7 +169,7 @@ exports[`Allow load custom resources md2html with metadata 4`] = ` @@ -241,5 +241,6 @@ items: - name: Config href: project/config.md base: . +deepBase: 0 " `; diff --git a/tests/e2e/__snapshots__/rtl.spec.ts.snap b/tests/e2e/__snapshots__/rtl.spec.ts.snap index 6e2e6f26..4f1f9bca 100644 --- a/tests/e2e/__snapshots__/rtl.spec.ts.snap +++ b/tests/e2e/__snapshots__/rtl.spec.ts.snap @@ -35,7 +35,7 @@ exports[`Generate html document with correct lang and dir attributes. Load corre @@ -141,7 +141,7 @@ exports[`Generate html document with correct lang and dir attributes. Load corre @@ -251,7 +251,7 @@ exports[`Generate html document with correct lang and dir attributes. Load corre