Refactor and add topic population

astro
Kamran Ahmed 2 years ago
parent 50b0309590
commit 9492c61955
  1. 15
      src/lib/path.ts
  2. 35
      src/lib/roadmap.ts
  3. 148
      src/lib/topic.ts
  4. 14
      src/pages/[...topicId].astro
  5. 2
      src/roadmaps/computer-science/content/117-databases/106-dcl.md

@ -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;
}

@ -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.

Loading…
Cancel
Save