1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-02 13:52:46 +02:00

Refactor and add topic population

This commit is contained in:
Kamran Ahmed
2023-01-01 20:53:51 +04:00
parent 50b0309590
commit 9492c61955
5 changed files with 172 additions and 42 deletions

15
src/lib/path.ts Normal file
View File

@@ -0,0 +1,15 @@
export function joinPath(...parts: string[]) {
const separator = '/';
return parts
.map((part: string, index: number) => {
if (index) {
part = part.replace(new RegExp('^' + separator), '');
}
if (index !== parts.length - 1) {
part = part.replace(new RegExp(separator + '$'), '');
}
return part;
})
.join(separator);
}

View File

@@ -35,37 +35,4 @@ export async function getRoadmapIds() {
return fileName.replace(".md", ""); return fileName.replace(".md", "");
}); });
} }
export interface TopicFileType {
frontMatter: Record<string, string>;
file: string;
url: string;
Content: any;
};
export async function getTopicPathMapping() {
const contentFiles = await import.meta.glob<string>(
"/src/roadmaps/*/content/**/*.md", {
eager: true
}
);
const mapping: Record<string, TopicFileType> = {};
Object.keys(contentFiles).forEach((filePath) => {
// => Sample Paths
// /src/roadmaps/vue/content/102-ecosystem/102-ssr/101-nuxt-js.md
// /src/roadmaps/vue/content/102-ecosystem/102-ssr/index.md
const url = filePath
.replace("/src/roadmaps/", "") // Remove the base `/src/roadmaps` from path
.replace("/content", "") // Remove the `/[roadmapName]/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
mapping[url] = contentFiles[filePath] as any;
});
return mapping;
}

148
src/lib/topic.ts Normal file
View File

@@ -0,0 +1,148 @@
import { joinPath } from './path';
import type { RoadmapFrontmatter } from './roadmap';
// Generates URL from the topic file path e.g.
// -> /src/roadmaps/vue/content/102-ecosystem/102-ssr/101-nuxt-js.md
// /vue/ecosystem/ssr/nuxt-js
// -> /src/roadmaps/vue/content/102-ecosystem
// /vue/ecosystem
function generateTopicUrl(filePath: string) {
return filePath
.replace('/src/roadmaps/', '/') // Remove the base `/src/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, TopicFileType>
): 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('/'));
}
}
const breadcrumbs = breadcrumbUrls.map((breadCrumbUrl): BreadcrumbItem => {
const topicFile = topicFiles[breadCrumbUrl];
const topicFileContent = topicFile.file;
const firstHeading = topicFileContent?.getHeadings()?.[0];
return { title: firstHeading?.text, url: breadCrumbUrl };
});
return breadcrumbs;
}
type BreadcrumbItem = {
title: string;
url: string;
};
type FileHeadingType = {
depth: number;
slug: string;
text: string;
};
export interface TopicFileContentType {
frontMatter: Record<string, string>;
file: string;
url: string;
Content: any;
getHeadings: () => FileHeadingType[];
}
export interface TopicFileType {
url: string;
file: TopicFileContentType;
roadmap: RoadmapFrontmatter;
roadmapId: string;
breadcrumbs: BreadcrumbItem[];
}
export async function getTopicFiles(): Promise<Record<string, TopicFileType>> {
const contentFiles = await import.meta.glob<string>(
'/src/roadmaps/*/content/**/*.md',
{
eager: true,
}
);
const mapping: Record<string, TopicFileType> = {};
for (let filePath of Object.keys(contentFiles)) {
const fileContent: TopicFileContentType = contentFiles[filePath] as any;
const fileHeadings = fileContent.getHeadings();
const firstHeading = fileHeadings[0];
const [, roadmapId, pathInsideContent] =
filePath.match(/^\/src\/roadmaps\/(.+)?\/content\/(.+)?$/) || [];
const topicUrl = generateTopicUrl(filePath);
const currentRoadmap = await import(
`../roadmaps/${roadmapId}/${roadmapId}.md`
);
mapping[topicUrl] = {
url: topicUrl,
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
const breadcrumbs: BreadcrumbItem[] = [
{
title: currentRoadmap.featuredTitle,
url: `${roadmapUrl}`,
},
{
title: 'Topics',
url: `${roadmapUrl}/topics`,
},
...generateBreadcrumbs(topicUrl, mapping),
];
mapping[topicUrl].breadcrumbs = breadcrumbs;
});
return mapping;
}

View File

@@ -1,27 +1,27 @@
--- ---
import MarkdownContent from "../components/MarkdownContent/MarkdownContent.astro"; import MarkdownContent from '../components/MarkdownContent/MarkdownContent.astro';
import BaseLayout from "../layouts/BaseLayout.astro"; import BaseLayout from '../layouts/BaseLayout.astro';
import { getTopicPathMapping, TopicFileType } from "../lib/roadmap"; import { getTopicFiles, TopicFileType } from '../lib/topic';
export async function getStaticPaths() { export async function getStaticPaths() {
const topicPathMapping = await getTopicPathMapping(); const topicPathMapping = await getTopicFiles();
// console.log(topicPathMapping); // console.log(topicPathMapping);
return Object.keys(topicPathMapping).map((topicSlug) => ({ return Object.keys(topicPathMapping).map((topicSlug) => ({
params: { topicId: topicSlug }, params: { topicId: topicSlug.replace(/^\//, '') },
props: topicPathMapping[topicSlug], props: topicPathMapping[topicSlug],
})); }));
} }
const { topicId } = Astro.params; const { topicId } = Astro.params;
const props: TopicFileType = Astro.props as any; const { file } = Astro.props as TopicFileType;
--- ---
<BaseLayout title="What is this"> <BaseLayout title="What is this">
<MarkdownContent> <MarkdownContent>
<main id="main-content"> <main id="main-content">
<props.Content /> <file.Content />
</main> </main>
</MarkdownContent> </MarkdownContent>
</BaseLayout> </BaseLayout>

View File

@@ -1,4 +1,4 @@
DCL (Data Control Language): # DCL (Data Control Language):
DCL includes commands such as GRANT and REVOKE which mainly deal with the rights, permissions, and other controls of the database system. DCL includes commands such as GRANT and REVOKE which mainly deal with the rights, permissions, and other controls of the database system.