mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-30 20:49:49 +02:00
Add rendering of best practices
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import type { BreadcrumbItem } from '../lib/topic';
|
||||
import type { BreadcrumbItem } from '../lib/roadmap-topic';
|
||||
|
||||
export interface Props {
|
||||
breadcrumbs: BreadcrumbItem[];
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import Loader from '../Loader.astro';
|
||||
import ShareIcons from '../ShareIcons.astro';
|
||||
import TopicOverlay from '../TopicOverlay.astro';
|
||||
import './InteractiveRoadmap.css';
|
||||
import './FrameRenderer.css';
|
||||
|
||||
export interface Props {
|
||||
roadmapId: string;
|
||||
@@ -17,27 +17,15 @@ export interface Props {
|
||||
const { roadmapId, jsonUrl, dimensions = null, description } = Astro.props;
|
||||
---
|
||||
|
||||
<link
|
||||
rel='preload'
|
||||
href='/fonts/balsamiq.woff2'
|
||||
as='font'
|
||||
type='font/woff2'
|
||||
crossorigin
|
||||
slot='after-header'
|
||||
/>
|
||||
<link rel='preload' href='/fonts/balsamiq.woff2' as='font' type='font/woff2' crossorigin slot='after-header' />
|
||||
|
||||
<div class='bg-gray-50 py-4 sm:py-12'>
|
||||
<div class='max-w-[1000px] container relative'>
|
||||
<ShareIcons
|
||||
description={description}
|
||||
pageUrl={`https://roadmap.sh/${roadmapId}`}
|
||||
/>
|
||||
<ShareIcons description={description} pageUrl={`https://roadmap.sh/${roadmapId}`} />
|
||||
<TopicOverlay roadmapId={roadmapId} />
|
||||
<div
|
||||
id='roadmap-svg'
|
||||
style={dimensions
|
||||
? `--aspect-ratio:${dimensions.width}/${dimensions.height}`
|
||||
: null}
|
||||
style={dimensions ? `--aspect-ratio:${dimensions.width}/${dimensions.height}` : null}
|
||||
data-roadmap-id={roadmapId}
|
||||
data-json-url={jsonUrl}
|
||||
>
|
||||
@@ -46,4 +34,4 @@ const { roadmapId, jsonUrl, dimensions = null, description } = Astro.props;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src='./roadmap.js'></script>
|
||||
<script src='./renderer.js'></script>
|
@@ -6,7 +6,7 @@ import { Sharer } from './sharer';
|
||||
* @typedef {{ roadmapId: string, jsonUrl: string }} RoadmapConfig
|
||||
*/
|
||||
|
||||
export class Roadmap {
|
||||
export class Renderer {
|
||||
/**
|
||||
* @param {RoadmapConfig} config
|
||||
*/
|
||||
@@ -98,8 +98,8 @@ export class Roadmap {
|
||||
}
|
||||
}
|
||||
|
||||
const roadmap = new Roadmap();
|
||||
roadmap.init();
|
||||
const renderer = new Renderer();
|
||||
renderer.init();
|
||||
|
||||
// Initialize the topic loader
|
||||
const topic = new Topic();
|
71
src/lib/best-practice-topic.ts
Normal file
71
src/lib/best-practice-topic.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { MarkdownFileType } from './file';
|
||||
import type { BestPracticeFrontmatter } from './best-pratice';
|
||||
|
||||
// Generates URL from the topic file path e.g.
|
||||
// -> /src/best-practices/frontend-performance/content/100-use-https-everywhere
|
||||
// /best-practices/frontend-performance/use-https-everywhere
|
||||
// -> /src/best-practices/frontend-performance/content/102-use-cdn-for-static-assets
|
||||
// /best-practices/use-cdn-for-static-assets
|
||||
function generateTopicUrl(filePath: string) {
|
||||
return filePath
|
||||
.replace('/src/best-practices/', '/') // Remove the base `/src/best-practices` from path
|
||||
.replace('/content', '') // Remove the `/[bestPracticeId]/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
|
||||
}
|
||||
|
||||
interface TopicFileType {
|
||||
url: string;
|
||||
heading: string;
|
||||
file: MarkdownFileType;
|
||||
bestPractice: BestPracticeFrontmatter;
|
||||
bestPracticeId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the topic files available for all the best practices
|
||||
* @returns Hashmap containing the topic slug and the topic file content
|
||||
*/
|
||||
async function getTopicFiles(): Promise<Record<string, TopicFileType>> {
|
||||
const contentFiles = await import.meta.glob<string>('/src/best-practices/*/content/**/*.md', {
|
||||
eager: true,
|
||||
});
|
||||
|
||||
const mapping: Record<string, TopicFileType> = {};
|
||||
|
||||
for (let filePath of Object.keys(contentFiles)) {
|
||||
const fileContent: MarkdownFileType = contentFiles[filePath] as any;
|
||||
const fileHeadings = fileContent.getHeadings();
|
||||
const firstHeading = fileHeadings[0];
|
||||
|
||||
const [, bestPracticeId] = filePath.match(/^\/src\/best-practices\/(.+)?\/content\/(.+)?$/) || [];
|
||||
const topicUrl = generateTopicUrl(filePath);
|
||||
|
||||
const currentBestPractice = await import(`../best-practices/${bestPracticeId}/${bestPracticeId}.md`);
|
||||
|
||||
mapping[topicUrl] = {
|
||||
url: topicUrl,
|
||||
heading: firstHeading?.text,
|
||||
file: fileContent,
|
||||
bestPractice: currentBestPractice.frontmatter,
|
||||
bestPracticeId: bestPracticeId,
|
||||
};
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the the topics for a given best practice
|
||||
*
|
||||
* @param bestPracticeId BestPractice id for which you want the topics
|
||||
*
|
||||
* @returns Promise<TopicFileType[]>
|
||||
*/
|
||||
export async function getTopicsByBestPracticeId(bestPracticeId: string): Promise<TopicFileType[]> {
|
||||
const topicFileMapping = await getTopicFiles();
|
||||
const allTopics = Object.values(topicFileMapping);
|
||||
|
||||
return allTopics.filter((topic) => topic.bestPracticeId === bestPracticeId);
|
||||
}
|
@@ -21,10 +21,7 @@ function generateTopicUrl(filePath: string) {
|
||||
* @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[] {
|
||||
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
|
||||
@@ -43,7 +40,7 @@ function generateBreadcrumbs(
|
||||
// -> [ '' ]
|
||||
// -> [ '', 'vue' ]
|
||||
if (subLinks.length > 2) {
|
||||
breadcrumbUrls.push(subLinks.join('/'));
|
||||
breadcrumbUrls.push(subLinks.join('/'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +61,7 @@ export type BreadcrumbItem = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export interface TopicFileType {
|
||||
export interface RoadmapTopicFileType {
|
||||
url: string;
|
||||
heading: string;
|
||||
file: MarkdownFileType;
|
||||
@@ -77,28 +74,22 @@ export interface TopicFileType {
|
||||
* Gets all the topic files available for all the roadmaps
|
||||
* @returns Hashmap containing the topic slug and the topic file content
|
||||
*/
|
||||
export async function getTopicFiles(): Promise<Record<string, TopicFileType>> {
|
||||
const contentFiles = await import.meta.glob<string>(
|
||||
'/src/roadmaps/*/content/**/*.md',
|
||||
{
|
||||
eager: true,
|
||||
}
|
||||
);
|
||||
export async function getRoadmapTopicFiles(): Promise<Record<string, RoadmapTopicFileType>> {
|
||||
const contentFiles = await import.meta.glob<string>('/src/roadmaps/*/content/**/*.md', {
|
||||
eager: true,
|
||||
});
|
||||
|
||||
const mapping: Record<string, TopicFileType> = {};
|
||||
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\/roadmaps\/(.+)?\/content\/(.+)?$/) || [];
|
||||
const [, roadmapId] = filePath.match(/^\/src\/roadmaps\/(.+)?\/content\/(.+)?$/) || [];
|
||||
const topicUrl = generateTopicUrl(filePath);
|
||||
|
||||
const currentRoadmap = await import(
|
||||
`../roadmaps/${roadmapId}/${roadmapId}.md`
|
||||
);
|
||||
const currentRoadmap = await import(`../roadmaps/${roadmapId}/${roadmapId}.md`);
|
||||
|
||||
mapping[topicUrl] = {
|
||||
url: topicUrl,
|
||||
@@ -112,11 +103,7 @@ export async function getTopicFiles(): Promise<Record<string, TopicFileType>> {
|
||||
|
||||
// Populate breadcrumbs inside the mapping
|
||||
Object.keys(mapping).forEach((topicUrl) => {
|
||||
const {
|
||||
roadmap: currentRoadmap,
|
||||
roadmapId,
|
||||
file: currentTopic,
|
||||
} = mapping[topicUrl];
|
||||
const { roadmap: currentRoadmap, roadmapId, file: currentTopic } = mapping[topicUrl];
|
||||
const roadmapUrl = `/${roadmapId}`;
|
||||
|
||||
// Breadcrumbs for the file
|
||||
@@ -170,15 +157,15 @@ export async function getTopicFiles(): Promise<Record<string, TopicFileType>> {
|
||||
// '/frontend/version-control-systems/basic-usage-of-git',
|
||||
// '/frontend/version-control-systems'
|
||||
// ]
|
||||
export async function sortTopics(topics: TopicFileType[]): Promise<TopicFileType[]> {
|
||||
let sortedTopics: TopicFileType[] = [];
|
||||
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];
|
||||
@@ -205,12 +192,10 @@ export async function sortTopics(topics: TopicFileType[]): Promise<TopicFileType
|
||||
* @param roadmapId Roadmap id for which you want the topics
|
||||
* @returns Promise<TopicFileType[]>
|
||||
*/
|
||||
export async function getTopicsByRoadmapId(roadmapId: string): Promise<TopicFileType[]> {
|
||||
const topicFileMapping = await getTopicFiles();
|
||||
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
|
||||
);
|
||||
const roadmapTopics = allTopics.filter((topic) => topic.roadmapId === roadmapId);
|
||||
|
||||
return sortTopics(roadmapTopics);
|
||||
}
|
||||
}
|
@@ -2,10 +2,10 @@
|
||||
import Breadcrumbs from '../../components/Breadcrumbs.astro';
|
||||
import RoadmapBanner from '../../components/RoadmapBanner.astro';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { getTopicFiles, TopicFileType } from '../../lib/topic';
|
||||
import { getRoadmapTopicFiles, RoadmapTopicFileType } from '../../lib/roadmap-topic';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const topicPathMapping = await getTopicFiles();
|
||||
const topicPathMapping = await getRoadmapTopicFiles();
|
||||
|
||||
return Object.keys(topicPathMapping).map((topicSlug) => {
|
||||
const topicDetails = topicPathMapping[topicSlug];
|
||||
@@ -23,7 +23,7 @@ export async function getStaticPaths() {
|
||||
}
|
||||
|
||||
const { topicId } = Astro.params;
|
||||
const { file, breadcrumbs, roadmapId, roadmap, heading } = Astro.props as TopicFileType;
|
||||
const { file, breadcrumbs, roadmapId, roadmap, heading } = Astro.props as RoadmapTopicFileType;
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
|
@@ -1,13 +1,13 @@
|
||||
---
|
||||
import CaptchaScripts from '../../components/Captcha/CaptchaScripts.astro';
|
||||
import FAQs from '../../components/FAQs/FAQs.astro';
|
||||
import InteractiveRoadmap from '../../components/InteractiveRoadmap/InteractiveRoadmap.astro';
|
||||
import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
|
||||
import MarkdownFile from '../../components/MarkdownFile.astro';
|
||||
import RoadmapHeader from '../../components/RoadmapHeader.astro';
|
||||
import UpcomingForm from '../../components/UpcomingForm.astro';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { generateArticleSchema,generateFAQSchema } from '../../lib/jsonld-schema';
|
||||
import { getRoadmapIds,RoadmapFrontmatter } from '../../lib/roadmap';
|
||||
import { generateArticleSchema, generateFAQSchema } from '../../lib/jsonld-schema';
|
||||
import { getRoadmapIds, RoadmapFrontmatter } from '../../lib/roadmap';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const roadmapIds = await getRoadmapIds();
|
||||
@@ -67,7 +67,7 @@ if (roadmapFAQs.length) {
|
||||
|
||||
{
|
||||
!roadmapData.isUpcoming && roadmapData.jsonUrl && (
|
||||
<InteractiveRoadmap
|
||||
<FrameRenderer
|
||||
roadmapId={roadmapId}
|
||||
description={roadmapData.description}
|
||||
jsonUrl={roadmapData.jsonUrl}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import RoadmapHeader from '../../components/RoadmapHeader.astro';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { getRoadmapIds, RoadmapFrontmatter } from '../../lib/roadmap';
|
||||
import { getTopicsByRoadmapId } from '../../lib/topic';
|
||||
import { getTopicsByRoadmapId } from '../../lib/roadmap-topic';
|
||||
|
||||
interface Params extends Record<string, string | undefined> {
|
||||
roadmapId: string;
|
||||
|
@@ -1,11 +1,11 @@
|
||||
---
|
||||
import BestPracticeHeader from '../../components/BestPracticeHeader.astro';
|
||||
import CaptchaScripts from '../../components/Captcha/CaptchaScripts.astro';
|
||||
import InteractiveRoadmap from '../../components/InteractiveRoadmap/InteractiveRoadmap.astro';
|
||||
import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
|
||||
import MarkdownFile from '../../components/MarkdownFile.astro';
|
||||
import UpcomingForm from '../../components/UpcomingForm.astro';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { BestPracticeFrontmatter,getBestPracticeIds } from '../../lib/best-pratice';
|
||||
import { BestPracticeFrontmatter, getBestPracticeIds } from '../../lib/best-pratice';
|
||||
import { generateArticleSchema } from '../../lib/jsonld-schema';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
@@ -59,7 +59,7 @@ if (bestPracticeData.schema) {
|
||||
|
||||
{
|
||||
!bestPracticeData.isUpcoming && bestPracticeData.jsonUrl && (
|
||||
<InteractiveRoadmap
|
||||
<FrameRenderer
|
||||
roadmapId={bestPracticeId}
|
||||
description={bestPracticeData.description}
|
||||
jsonUrl={bestPracticeData.jsonUrl}
|
||||
|
Reference in New Issue
Block a user