Skip to content

Commit

Permalink
Merge pull request #110 from DenverCoder544/internal_linking_vol_n
Browse files Browse the repository at this point in the history
Internal linking vol n
  • Loading branch information
ZakarFin authored Sep 2, 2024
2 parents 4ad9cbc + 2fbcc3a commit f541249
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 11 deletions.
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

0 comments on commit f541249

Please sign in to comment.