mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-01 21:32:35 +02:00
Refactor and add topic population
This commit is contained in:
15
src/lib/path.ts
Normal file
15
src/lib/path.ts
Normal 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);
|
||||
}
|
@@ -35,37 +35,4 @@ export async function getRoadmapIds() {
|
||||
|
||||
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
148
src/lib/topic.ts
Normal 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;
|
||||
}
|
@@ -1,27 +1,27 @@
|
||||
---
|
||||
import MarkdownContent from "../components/MarkdownContent/MarkdownContent.astro";
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import { getTopicPathMapping, TopicFileType } from "../lib/roadmap";
|
||||
import MarkdownContent from '../components/MarkdownContent/MarkdownContent.astro';
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import { getTopicFiles, TopicFileType } from '../lib/topic';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const topicPathMapping = await getTopicPathMapping();
|
||||
const topicPathMapping = await getTopicFiles();
|
||||
|
||||
// console.log(topicPathMapping);
|
||||
|
||||
return Object.keys(topicPathMapping).map((topicSlug) => ({
|
||||
params: { topicId: topicSlug },
|
||||
params: { topicId: topicSlug.replace(/^\//, '') },
|
||||
props: topicPathMapping[topicSlug],
|
||||
}));
|
||||
}
|
||||
|
||||
const { topicId } = Astro.params;
|
||||
const props: TopicFileType = Astro.props as any;
|
||||
const { file } = Astro.props as TopicFileType;
|
||||
---
|
||||
|
||||
<BaseLayout title="What is this">
|
||||
<MarkdownContent>
|
||||
<main id="main-content">
|
||||
<props.Content />
|
||||
<file.Content />
|
||||
</main>
|
||||
</MarkdownContent>
|
||||
</BaseLayout>
|
||||
|
@@ -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.
|
||||
|
||||
|
Reference in New Issue
Block a user