Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal linking vol n #110

Merged
merged 4 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/documentation/docs/[version]/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default async function SingleDocPage({
}

await indexJSON.forEach(async (element: MarkdownFileMetadata) => {
const { html, anchorLinks } = await readAndConcatMarkdownFiles(element, imagesRuntimePath);
const { html, anchorLinks } = await readAndConcatMarkdownFiles(element, imagesRuntimePath, indexJSON, element.title);
element.anchorLinks = anchorLinks;
element.html = html;
});
Expand Down
78 changes: 77 additions & 1 deletion lib/markdownToHtml.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { badgeTemplates, insertIdsToHeaders, processAllLinks, processCodeBlocks, processHeaders, processJavascriptBlocks, processMigrationGuideLinks, processTripleQuoteCodeBlocks, updateMarkdownHtmlStyleTags, updateMarkdownImagePaths } from "./markdownToHtml";
import { badgeTemplates, insertIdsToHeaders, processAllLinks, processCodeBlocks, processHeaders, processInternalMDLinks, processJavascriptBlocks, processMigrationGuideLinks, processTripleQuoteCodeBlocks, updateMarkdownHtmlStyleTags, updateMarkdownImagePaths } from "./markdownToHtml";
import slugify from 'slugify';

const createTestHtml = () => {
Expand Down Expand Up @@ -236,4 +236,80 @@ describe('markdownToHtml tests', () => {
expect(processed).toEqual(markdown);
});
})

describe("processInternalMDLinks", function() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const indexJSON: any[] = [
{
"slug": "section-one",
"title": "Section One",
"children": [
{
"fileName": "file1.md",
"anchor": "anchor1"
},
{
"fileName": "file2.md",
"anchor": "anchor2"
}
]
},
{
"slug": "section-two",
"title": "Section Two",
"children": [
{
"fileName": "file3.md",
"anchor": "anchor3"
},
{
"fileName": "file4.md",
"anchor": "anchor4"
}
]
}
];

it("replaces a relative link with the correct link from indexJSON", function() {
const markdownContent = "Check this [link](file1.md) in the content.";
const activeSectionTitle = "Section One";
const result = processInternalMDLinks(markdownContent, indexJSON, activeSectionTitle);
expect(result).toBe("Check this [link](section-one#anchor1) in the content.");
});

it("does not replace external links", function() {
const markdownContent = "Visit [Google](https://www.google.com) for more info.";
const activeSectionTitle = "Section One";
const result = processInternalMDLinks(markdownContent, indexJSON, activeSectionTitle);
expect(result).toBe("Visit [Google](https://www.google.com) for more info.");
});

it("replaces a relative link with a path and correct link from indexJSON", function() {
const markdownContent = "More info [here](../Section Two/file3.md).";
const activeSectionTitle = "Section One";
const result = processInternalMDLinks(markdownContent, indexJSON, activeSectionTitle);
expect(result).toBe("More info [here](section-two#anchor3).");
});

it("does not replace a link if no matching file is found in indexJSON", function() {
const markdownContent = "Check this [link](nonexistent.md) in the content.";
const activeSectionTitle = "Section One";
const result = processInternalMDLinks(markdownContent, indexJSON, activeSectionTitle);
expect(result).toBe("Check this [link](nonexistent.md) in the content.");
});

it("handles multiple links in the same content", function() {
const markdownContent = "Links: [file1](file1.md), [file4](../Section Two/file4.md), and [external](https://www.example.com).";
const activeSectionTitle = "Section One";
const result = processInternalMDLinks(markdownContent, indexJSON, activeSectionTitle);
expect(result).toBe("Links: [file1](section-one#anchor1), [file4](section-two#anchor4), and [external](https://www.example.com).");
});

it("leaves content unchanged if there are no links", function() {
const markdownContent = "This content has no links.";
const activeSectionTitle = "Section One";
const result = processInternalMDLinks(markdownContent, indexJSON, activeSectionTitle);
expect(result).toBe("This content has no links.");
});
});
});
83 changes: 81 additions & 2 deletions lib/markdownToHtml.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DocAnchorLinksType } from '@/types/types';
import { DocAnchorLinksType, MarkdownFileMetadata } from '@/types/types';
import slugify from 'slugify';
import hljs from 'highlight.js'

Expand Down Expand Up @@ -148,6 +148,7 @@ export const processMigrationGuideLinks = (markdownContent: string): string => {
return result;
}

/*
const findLinksToMDDocs = (mdString: string) => {
//find all html-style links that have data-internal-anchor - attribute set
//and replace thos with links to internal anchors
Expand Down Expand Up @@ -201,4 +202,82 @@ const parseRef = (ref: string): string => {
}

return '#' + slugify(ref);
}
}

*/



/**
* Creates a slugified url for use in documentation section
* @param {string} url Original href of link
* @param {Object[]} indexJSON The metadata JSON-structure of the documentation
* @param {string} activeSectionTitle Title of the active section when linking within same folder
* @returns {string} Return a new anchor to use as href
*/
const createSlugifiedUrlToAnchor = (url: string, indexJSON: MarkdownFileMetadata[], activeSectionTitle: string) => {
if (url.startsWith('../')) {
url = url.replace('../', '');
}
const urlParts = url.split('/');
const fileName = urlParts.pop();
const directory = urlParts.length > 0 ? urlParts.join('/') : '';
const findChildAnchor = (children: MarkdownFileMetadata[], fileName: string) => {
const child = children.find(child => child.fileName === fileName);
return child ? child.anchor : null;
};

if (!fileName) {
return '';
}

// No path provided -> find active section by title
if (!directory) {
const activeSection = indexJSON.find(item => item.title === activeSectionTitle);
if (activeSection) {
const anchor = findChildAnchor(activeSection.children, fileName);
if (anchor) {
return `${activeSection.slug}#${anchor}`;
}

//console.log('No corresponding anchor found under active section: ', activeSectionTitle, fileName);
}
} else {
// Find section corresponding to path
const matchingTopLevel = indexJSON.find(item => item.title === directory);
if (matchingTopLevel) {
const anchor = findChildAnchor(matchingTopLevel.children, fileName);
if (anchor) {
return `${matchingTopLevel.slug}#${anchor}`;
}

//console.log('No corresponding anchor found under section: ', matchingTopLevel, fileName);
}
}

return url;
}

/**
* Find relative links to .md docs in content and replace href with an anchor we've parsed in documentation metadata
* @param {string} markdownContent - The content of the markdown file
* @param {JSON-object} indexJSON documentation version full metadata
* @param {activeSectionTitle} title of current section when linking within the same folder
* @returns {string} content with links replaced
*/
export const processInternalMDLinks = (markdownContent: string, indexJSON: MarkdownFileMetadata[], activeSectionTitle: string): string => {
// Regular expression to match Markdown links
const relativeLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
const replacedContent = markdownContent.replace(relativeLinkRegex, (match, text, url) => {
// Check if the URL is a relative link ending with .md
if (url.endsWith('.md') && !url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('www.')) {
const anchor = createSlugifiedUrlToAnchor(url, indexJSON, activeSectionTitle);
return `[${text}](${anchor})`;
}
// Return the original match if it's not a relative .md link
return match;
});

return replacedContent;
}

15 changes: 10 additions & 5 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const readMarkdownFile = async function(filePath: string, imagesPath: str
return markdown;
};

export const readAndConcatMarkdownFiles = async function(parentItem: MarkdownFileMetadata, imagesPath: string = '') {
export const readAndConcatMarkdownFiles = async function(parentItem: MarkdownFileMetadata, imagesPath: string = '', indexJSON: MarkdownFileMetadata[] = [], activeSectionSlug: string = '') {
let markdownAll = '';
parentItem.children.forEach(element => {
const cwdPath = path.resolve(process.cwd());
Expand All @@ -53,7 +53,7 @@ export const readAndConcatMarkdownFiles = async function(parentItem: MarkdownFil
markdownAll += content +'\r\n\r\n';
});

markdownAll = processMarkdown(markdownAll, imagesPath);
markdownAll = processMarkdown(markdownAll, imagesPath, indexJSON, activeSectionSlug);

const compiled = compileMarkdownToHTML(markdownAll, parentItem.ordinal || '1');
// inject script to make mermaid js work its magic
Expand Down Expand Up @@ -90,16 +90,21 @@ const replaceLevelOneHeadingsWithLevelTwo = (markdown: string): string => {
return replacedString;
}

const processMarkdown = (markdown: string, imagesPath: string) => {
const processMarkdown = (markdown: string, imagesPath: string, indexJSON: MarkdownFileMetadata[] = [], activeSectionTitle: string = '') => {
markdown = updateMarkdownImagePaths(markdown, imagesPath);
markdown = updateMarkdownHtmlStyleTags(markdown);
markdown = processHeaders(markdown);
// migration guide first, specific treatment for that
markdown = processMigrationGuideLinks(markdown);

//documentation internal links to other mds to work with the compiled version
//Note! At this point the following condition is true only for documentation-section
if (indexJSON && activeSectionTitle) {
markdown = processInternalMDLinks(markdown, indexJSON, activeSectionTitle);
}

//process rest of the links from md style -> <a href... to help the lib that's supposed to be doing this in its efforts.
markdown = processAllLinks(markdown);
//documentation internal links to other mds to work with the compiled version
markdown = processInternalMDLinks(markdown);

// these are dependent to be run in this order
markdown = processJavascriptBlocks(markdown);
Expand Down
26 changes: 25 additions & 1 deletion scripts/generateDocumentationMetadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,27 @@ function sortByParagraphNumber(a, b) {
return aParts.length - bParts.length;
}

/**
* Return the content of the first heading of a markdwon file slugified.
*/
function getAnchorForFile(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf-8');
const headingMatch = content.match(/^(#{1,3}) (.+)/m);

if (headingMatch && headingMatch[2]) {
const headingText = headingMatch[2].trim();
const slug = slugify(headingText);
return slug;
} else {
return null;
}
} catch (error) {
console.error('Error reading file!:', error);
return null;
}
}

function listContentsRecursively(fullPath, docsRelativePath, results = []) {
const filesAndDirectories = fs.readdirSync(fullPath, { withFileTypes: true });
filesAndDirectories.sort(sortByParagraphNumber);
Expand All @@ -44,15 +65,18 @@ function listContentsRecursively(fullPath, docsRelativePath, results = []) {
});
} else {
if (path.extname(itemPath).toLowerCase() === '.md') {

let fileNameWithoutExtension = path.parse(item.name).name;
if (fileNameWithoutExtension.indexOf('-') > -1) {
fileNameWithoutExtension = fileNameWithoutExtension.split('-')[1] || fileNameWithoutExtension;
}
const slug = slugify(fileNameWithoutExtension);
const anchor = getAnchorForFile(itemPath);
results.push({
path: itemRelativePath,
fileName: item.name,
slug
slug,
anchor: anchor ? anchor : null
});
}
}
Expand Down
3 changes: 2 additions & 1 deletion types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export type MarkdownFileMetadata = {
title: string,
anchorLinks: Array<DocAnchorLinksType>,
children: Array<MarkdownFileMetadata>,
html: string
html: string,
anchor?: string
}

export type VersionedResourceLink = {
Expand Down
Loading