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