computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
6.4 KiB
219 lines
6.4 KiB
import type {MarkdownFileType} from './file'; |
|
import type {RoadmapFrontmatter} from './roadmap'; |
|
|
|
// Generates URL from the topic file path e.g. |
|
// -> /src/data/roadmaps/vue/content/102-ecosystem/102-ssr/101-nuxt-js.md |
|
// /vue/ecosystem/ssr/nuxt-js |
|
// -> /src/data/roadmaps/vue/content/102-ecosystem |
|
// /vue/ecosystem |
|
function generateTopicUrl(filePath: string) { |
|
return filePath |
|
.replace('/src/data/roadmaps/', '/') // Remove the base `/src/data/roadmaps` from path |
|
.replace('/content', '') // Remove the `/[roadmapId]/content` |
|
.replace(/\/\d+-/g, '/') // Remove ordering info `/101-ecosystem` |
|
.replace(/\/index\.md$/, '') // Make the `/index.md` to become the parent folder only |
|
.replace(/\.md$/, ''); // Remove `.md` from the end of file |
|
} |
|
|
|
/** |
|
* Generates breadcrumbs for the given topic URL from the given topic file details |
|
* |
|
* @param topicUrl Topic URL for which breadcrumbs are required |
|
* @param topicFiles Topic file mapping to read the topic data from |
|
*/ |
|
function generateBreadcrumbs( |
|
topicUrl: string, |
|
topicFiles: Record<string, RoadmapTopicFileType> |
|
): BreadcrumbItem[] { |
|
// We need to collect all the pages with permalinks to generate breadcrumbs |
|
// e.g. /backend/internet/how-does-internet-work/http |
|
// /backend |
|
// /backend/internet |
|
// /backend/internet/how-does-internet-work |
|
// /backend/internet/how-does-internet-work/http |
|
|
|
const urlParts = topicUrl.split('/'); |
|
const breadcrumbUrls = []; |
|
const subLinks = []; |
|
|
|
for (let counter = 0; counter < urlParts.length; counter++) { |
|
subLinks.push(urlParts[counter]); |
|
|
|
// Skip the following |
|
// -> [ '' ] |
|
// -> [ '', 'vue' ] |
|
if (subLinks.length > 2) { |
|
breadcrumbUrls.push(subLinks.join('/')); |
|
} |
|
} |
|
|
|
return breadcrumbUrls.map((breadCrumbUrl): BreadcrumbItem => { |
|
const topicFile = topicFiles[breadCrumbUrl]; |
|
|
|
const topicFileContent = topicFile?.file; |
|
|
|
const firstHeading = topicFileContent?.getHeadings()?.[0]; |
|
|
|
return {title: firstHeading?.text, url: breadCrumbUrl}; |
|
}); |
|
} |
|
|
|
export type BreadcrumbItem = { |
|
title: string; |
|
url: string; |
|
}; |
|
|
|
export interface RoadmapTopicFileType { |
|
url: string; |
|
heading: string; |
|
file: MarkdownFileType; |
|
roadmap: RoadmapFrontmatter; |
|
roadmapId: string; |
|
breadcrumbs: BreadcrumbItem[]; |
|
} |
|
|
|
/** |
|
* Gets all the topic files available for all the roadmaps |
|
* @returns Hashmap containing the topic slug and the topic file content |
|
*/ |
|
export async function getRoadmapTopicFiles(): Promise< |
|
Record<string, RoadmapTopicFileType> |
|
> { |
|
const contentFiles = await import.meta.glob<string>( |
|
'/src/data/roadmaps/*/content/**/*.md', |
|
{ |
|
eager: true, |
|
} |
|
); |
|
|
|
const mapping: Record<string, RoadmapTopicFileType> = {}; |
|
|
|
for (let filePath of Object.keys(contentFiles)) { |
|
const fileContent: MarkdownFileType = contentFiles[filePath] as any; |
|
const fileHeadings = fileContent.getHeadings(); |
|
const firstHeading = fileHeadings[0]; |
|
|
|
const [, roadmapId] = |
|
filePath.match(/^\/src\/data\/roadmaps\/(.+)?\/content\/(.+)?$/) || []; |
|
const topicUrl = generateTopicUrl(filePath); |
|
|
|
const currentRoadmap = await import( |
|
`../data/roadmaps/${roadmapId}/${roadmapId}.md` |
|
); |
|
|
|
mapping[topicUrl] = { |
|
url: topicUrl, |
|
heading: firstHeading?.text, |
|
file: fileContent, |
|
roadmap: currentRoadmap.frontmatter, |
|
roadmapId: roadmapId, |
|
breadcrumbs: [], |
|
}; |
|
} |
|
|
|
// Populate breadcrumbs inside the mapping |
|
Object.keys(mapping).forEach((topicUrl) => { |
|
const { |
|
roadmap: currentRoadmap, |
|
roadmapId, |
|
file: currentTopic, |
|
} = mapping[topicUrl]; |
|
const roadmapUrl = `/${roadmapId}`; |
|
|
|
// Breadcrumbs for the file |
|
mapping[topicUrl].breadcrumbs = [ |
|
{ |
|
title: 'Roadmaps', |
|
url: '/roadmaps', |
|
}, |
|
{ |
|
title: currentRoadmap.briefTitle, |
|
url: `${roadmapUrl}`, |
|
}, |
|
{ |
|
title: 'Topics', |
|
url: `${roadmapUrl}/topics`, |
|
}, |
|
...generateBreadcrumbs(topicUrl, mapping), |
|
]; |
|
}); |
|
|
|
return mapping; |
|
} |
|
|
|
// [ |
|
// '/frontend/internet/how-does-the-internet-work', |
|
// '/frontend/internet/what-is-http', |
|
// '/frontend/internet/browsers-and-how-they-work', |
|
// '/frontend/internet/dns-and-how-it-works', |
|
// '/frontend/internet/what-is-domain-name', |
|
// '/frontend/internet/what-is-hosting', |
|
// '/frontend/internet', |
|
// '/frontend/html/learn-the-basics', |
|
// '/frontend/html/writing-semantic-html', |
|
// '/frontend/html/forms-and-validations', |
|
// '/frontend/html/conventions-and-best-practices', |
|
// '/frontend/html/accessibility', |
|
// '/frontend/html/seo-basics', |
|
// '/frontend/html', |
|
// '/frontend/css/learn-the-basics', |
|
// '/frontend/css/making-layouts', |
|
// '/frontend/css/responsive-design-and-media-queries', |
|
// '/frontend/css', |
|
// '/frontend/javascript/syntax-and-basic-constructs', |
|
// '/frontend/javascript/learn-dom-manipulation', |
|
// '/frontend/javascript/learn-fetch-api-ajax-xhr', |
|
// '/frontend/javascript/es6-and-modular-javascript', |
|
// '/frontend/javascript/concepts', |
|
// '/frontend/javascript', |
|
// '/frontend/version-control-systems/basic-usage-of-git', |
|
// '/frontend/version-control-systems' |
|
// ] |
|
async function sortTopics( |
|
topics: RoadmapTopicFileType[] |
|
): Promise<RoadmapTopicFileType[]> { |
|
let sortedTopics: RoadmapTopicFileType[] = []; |
|
|
|
// For each of the topic, find its place in the sorted topics |
|
for (let i = 0; i < topics.length; i++) { |
|
const currTopic = topics[i]; |
|
const currUrl = currTopic.url; |
|
let isPlaced = false; |
|
|
|
// Find the first sorted topic which starts with the current topic |
|
for (let j = 0; j < sortedTopics.length; j++) { |
|
const sortedTopic = sortedTopics[j]; |
|
const sortedUrl = sortedTopic.url; |
|
|
|
// Insert before the current URL and break |
|
if (sortedUrl.startsWith(`${currUrl}/`)) { |
|
sortedTopics.splice(j, 0, currTopic); |
|
isPlaced = true; |
|
break; |
|
} |
|
} |
|
|
|
if (!isPlaced) { |
|
sortedTopics.push(currTopic); |
|
} |
|
} |
|
|
|
return sortedTopics; |
|
} |
|
|
|
/** |
|
* Gets the the topics for a given roadmap |
|
* @param roadmapId Roadmap id for which you want the topics |
|
* @returns Promise<TopicFileType[]> |
|
*/ |
|
export async function getTopicsByRoadmapId( |
|
roadmapId: string |
|
): Promise<RoadmapTopicFileType[]> { |
|
const topicFileMapping = await getRoadmapTopicFiles(); |
|
const allTopics = Object.values(topicFileMapping); |
|
const roadmapTopics = allTopics.filter( |
|
(topic) => topic.roadmapId === roadmapId |
|
); |
|
|
|
return sortTopics(roadmapTopics); |
|
}
|
|
|