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:
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", "");
|
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 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>
|
||||||
|
@@ -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.
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user