Add rendering of best practices

pull/3356/head
Kamran Ahmed 2 years ago
parent e8d2bd00c6
commit f9db9bee95
  1. 2033
      public/jsons/best-practices/frontend-performance.json
  2. 2
      src/components/Breadcrumbs.astro
  3. 22
      src/components/FrameRenderer/FrameRenderer.astro
  4. 0
      src/components/FrameRenderer/FrameRenderer.css
  5. 6
      src/components/FrameRenderer/renderer.js
  6. 0
      src/components/FrameRenderer/sharer.js
  7. 0
      src/components/FrameRenderer/topic.js
  8. 71
      src/lib/best-practice-topic.ts
  9. 47
      src/lib/roadmap-topic.ts
  10. 6
      src/pages/[roadmapId]/[...topicId].astro
  11. 8
      src/pages/[roadmapId]/index.astro
  12. 2
      src/pages/[roadmapId]/topics.astro
  13. 6
      src/pages/best-practices/[bestPracticeId].astro

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 { export interface Props {
breadcrumbs: BreadcrumbItem[]; breadcrumbs: BreadcrumbItem[];

@ -2,7 +2,7 @@
import Loader from '../Loader.astro'; import Loader from '../Loader.astro';
import ShareIcons from '../ShareIcons.astro'; import ShareIcons from '../ShareIcons.astro';
import TopicOverlay from '../TopicOverlay.astro'; import TopicOverlay from '../TopicOverlay.astro';
import './InteractiveRoadmap.css'; import './FrameRenderer.css';
export interface Props { export interface Props {
roadmapId: string; roadmapId: string;
@ -17,27 +17,15 @@ export interface Props {
const { roadmapId, jsonUrl, dimensions = null, description } = Astro.props; const { roadmapId, jsonUrl, dimensions = null, description } = Astro.props;
--- ---
<link <link rel='preload' href='/fonts/balsamiq.woff2' as='font' type='font/woff2' crossorigin slot='after-header' />
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='bg-gray-50 py-4 sm:py-12'>
<div class='max-w-[1000px] container relative'> <div class='max-w-[1000px] container relative'>
<ShareIcons <ShareIcons description={description} pageUrl={`https://roadmap.sh/${roadmapId}`} />
description={description}
pageUrl={`https://roadmap.sh/${roadmapId}`}
/>
<TopicOverlay roadmapId={roadmapId} /> <TopicOverlay roadmapId={roadmapId} />
<div <div
id='roadmap-svg' id='roadmap-svg'
style={dimensions style={dimensions ? `--aspect-ratio:${dimensions.width}/${dimensions.height}` : null}
? `--aspect-ratio:${dimensions.width}/${dimensions.height}`
: null}
data-roadmap-id={roadmapId} data-roadmap-id={roadmapId}
data-json-url={jsonUrl} data-json-url={jsonUrl}
> >
@ -46,4 +34,4 @@ const { roadmapId, jsonUrl, dimensions = null, description } = Astro.props;
</div> </div>
</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 * @typedef {{ roadmapId: string, jsonUrl: string }} RoadmapConfig
*/ */
export class Roadmap { export class Renderer {
/** /**
* @param {RoadmapConfig} config * @param {RoadmapConfig} config
*/ */
@ -98,8 +98,8 @@ export class Roadmap {
} }
} }
const roadmap = new Roadmap(); const renderer = new Renderer();
roadmap.init(); renderer.init();
// Initialize the topic loader // Initialize the topic loader
const topic = new Topic(); const topic = new Topic();

@ -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 topicUrl Topic URL for which breadcrumbs are required
* @param topicFiles Topic file mapping to read the topic data from * @param topicFiles Topic file mapping to read the topic data from
*/ */
function generateBreadcrumbs( function generateBreadcrumbs(topicUrl: string, topicFiles: Record<string, RoadmapTopicFileType>): BreadcrumbItem[] {
topicUrl: string,
topicFiles: Record<string, TopicFileType>
): BreadcrumbItem[] {
// We need to collect all the pages with permalinks to generate breadcrumbs // We need to collect all the pages with permalinks to generate breadcrumbs
// e.g. /backend/internet/how-does-internet-work/http // e.g. /backend/internet/how-does-internet-work/http
// /backend // /backend
@ -43,7 +40,7 @@ function generateBreadcrumbs(
// -> [ '' ] // -> [ '' ]
// -> [ '', 'vue' ] // -> [ '', 'vue' ]
if (subLinks.length > 2) { if (subLinks.length > 2) {
breadcrumbUrls.push(subLinks.join('/')); breadcrumbUrls.push(subLinks.join('/'));
} }
} }
@ -64,7 +61,7 @@ export type BreadcrumbItem = {
url: string; url: string;
}; };
export interface TopicFileType { export interface RoadmapTopicFileType {
url: string; url: string;
heading: string; heading: string;
file: MarkdownFileType; file: MarkdownFileType;
@ -77,28 +74,22 @@ export interface TopicFileType {
* Gets all the topic files available for all the roadmaps * Gets all the topic files available for all the roadmaps
* @returns Hashmap containing the topic slug and the topic file content * @returns Hashmap containing the topic slug and the topic file content
*/ */
export async function getTopicFiles(): Promise<Record<string, TopicFileType>> { export async function getRoadmapTopicFiles(): Promise<Record<string, RoadmapTopicFileType>> {
const contentFiles = await import.meta.glob<string>( const contentFiles = await import.meta.glob<string>('/src/roadmaps/*/content/**/*.md', {
'/src/roadmaps/*/content/**/*.md', eager: true,
{ });
eager: true,
}
);
const mapping: Record<string, TopicFileType> = {}; const mapping: Record<string, RoadmapTopicFileType> = {};
for (let filePath of Object.keys(contentFiles)) { for (let filePath of Object.keys(contentFiles)) {
const fileContent: MarkdownFileType = contentFiles[filePath] as any; const fileContent: MarkdownFileType = contentFiles[filePath] as any;
const fileHeadings = fileContent.getHeadings(); const fileHeadings = fileContent.getHeadings();
const firstHeading = fileHeadings[0]; const firstHeading = fileHeadings[0];
const [, roadmapId] = const [, roadmapId] = filePath.match(/^\/src\/roadmaps\/(.+)?\/content\/(.+)?$/) || [];
filePath.match(/^\/src\/roadmaps\/(.+)?\/content\/(.+)?$/) || [];
const topicUrl = generateTopicUrl(filePath); const topicUrl = generateTopicUrl(filePath);
const currentRoadmap = await import( const currentRoadmap = await import(`../roadmaps/${roadmapId}/${roadmapId}.md`);
`../roadmaps/${roadmapId}/${roadmapId}.md`
);
mapping[topicUrl] = { mapping[topicUrl] = {
url: topicUrl, url: topicUrl,
@ -112,11 +103,7 @@ export async function getTopicFiles(): Promise<Record<string, TopicFileType>> {
// Populate breadcrumbs inside the mapping // Populate breadcrumbs inside the mapping
Object.keys(mapping).forEach((topicUrl) => { Object.keys(mapping).forEach((topicUrl) => {
const { const { roadmap: currentRoadmap, roadmapId, file: currentTopic } = mapping[topicUrl];
roadmap: currentRoadmap,
roadmapId,
file: currentTopic,
} = mapping[topicUrl];
const roadmapUrl = `/${roadmapId}`; const roadmapUrl = `/${roadmapId}`;
// Breadcrumbs for the file // Breadcrumbs for the file
@ -170,8 +157,8 @@ export async function getTopicFiles(): Promise<Record<string, TopicFileType>> {
// '/frontend/version-control-systems/basic-usage-of-git', // '/frontend/version-control-systems/basic-usage-of-git',
// '/frontend/version-control-systems' // '/frontend/version-control-systems'
// ] // ]
export async function sortTopics(topics: TopicFileType[]): Promise<TopicFileType[]> { async function sortTopics(topics: RoadmapTopicFileType[]): Promise<RoadmapTopicFileType[]> {
let sortedTopics: TopicFileType[] = []; let sortedTopics: RoadmapTopicFileType[] = [];
// For each of the topic, find its place in the sorted topics // For each of the topic, find its place in the sorted topics
for (let i = 0; i < topics.length; i++) { for (let i = 0; i < topics.length; i++) {
@ -205,12 +192,10 @@ export async function sortTopics(topics: TopicFileType[]): Promise<TopicFileType
* @param roadmapId Roadmap id for which you want the topics * @param roadmapId Roadmap id for which you want the topics
* @returns Promise<TopicFileType[]> * @returns Promise<TopicFileType[]>
*/ */
export async function getTopicsByRoadmapId(roadmapId: string): Promise<TopicFileType[]> { export async function getTopicsByRoadmapId(roadmapId: string): Promise<RoadmapTopicFileType[]> {
const topicFileMapping = await getTopicFiles(); const topicFileMapping = await getRoadmapTopicFiles();
const allTopics = Object.values(topicFileMapping); const allTopics = Object.values(topicFileMapping);
const roadmapTopics = allTopics.filter( const roadmapTopics = allTopics.filter((topic) => topic.roadmapId === roadmapId);
(topic) => topic.roadmapId === roadmapId
);
return sortTopics(roadmapTopics); return sortTopics(roadmapTopics);
} }

@ -2,10 +2,10 @@
import Breadcrumbs from '../../components/Breadcrumbs.astro'; import Breadcrumbs from '../../components/Breadcrumbs.astro';
import RoadmapBanner from '../../components/RoadmapBanner.astro'; import RoadmapBanner from '../../components/RoadmapBanner.astro';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { getTopicFiles, TopicFileType } from '../../lib/topic'; import { getRoadmapTopicFiles, RoadmapTopicFileType } from '../../lib/roadmap-topic';
export async function getStaticPaths() { export async function getStaticPaths() {
const topicPathMapping = await getTopicFiles(); const topicPathMapping = await getRoadmapTopicFiles();
return Object.keys(topicPathMapping).map((topicSlug) => { return Object.keys(topicPathMapping).map((topicSlug) => {
const topicDetails = topicPathMapping[topicSlug]; const topicDetails = topicPathMapping[topicSlug];
@ -23,7 +23,7 @@ export async function getStaticPaths() {
} }
const { topicId } = Astro.params; 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 <BaseLayout

@ -1,13 +1,13 @@
--- ---
import CaptchaScripts from '../../components/Captcha/CaptchaScripts.astro'; import CaptchaScripts from '../../components/Captcha/CaptchaScripts.astro';
import FAQs from '../../components/FAQs/FAQs.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 MarkdownFile from '../../components/MarkdownFile.astro';
import RoadmapHeader from '../../components/RoadmapHeader.astro'; import RoadmapHeader from '../../components/RoadmapHeader.astro';
import UpcomingForm from '../../components/UpcomingForm.astro'; import UpcomingForm from '../../components/UpcomingForm.astro';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { generateArticleSchema,generateFAQSchema } from '../../lib/jsonld-schema'; import { generateArticleSchema, generateFAQSchema } from '../../lib/jsonld-schema';
import { getRoadmapIds,RoadmapFrontmatter } from '../../lib/roadmap'; import { getRoadmapIds, RoadmapFrontmatter } from '../../lib/roadmap';
export async function getStaticPaths() { export async function getStaticPaths() {
const roadmapIds = await getRoadmapIds(); const roadmapIds = await getRoadmapIds();
@ -67,7 +67,7 @@ if (roadmapFAQs.length) {
{ {
!roadmapData.isUpcoming && roadmapData.jsonUrl && ( !roadmapData.isUpcoming && roadmapData.jsonUrl && (
<InteractiveRoadmap <FrameRenderer
roadmapId={roadmapId} roadmapId={roadmapId}
description={roadmapData.description} description={roadmapData.description}
jsonUrl={roadmapData.jsonUrl} jsonUrl={roadmapData.jsonUrl}

@ -2,7 +2,7 @@
import RoadmapHeader from '../../components/RoadmapHeader.astro'; import RoadmapHeader from '../../components/RoadmapHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { getRoadmapIds, RoadmapFrontmatter } from '../../lib/roadmap'; import { getRoadmapIds, RoadmapFrontmatter } from '../../lib/roadmap';
import { getTopicsByRoadmapId } from '../../lib/topic'; import { getTopicsByRoadmapId } from '../../lib/roadmap-topic';
interface Params extends Record<string, string | undefined> { interface Params extends Record<string, string | undefined> {
roadmapId: string; roadmapId: string;

@ -1,11 +1,11 @@
--- ---
import BestPracticeHeader from '../../components/BestPracticeHeader.astro'; import BestPracticeHeader from '../../components/BestPracticeHeader.astro';
import CaptchaScripts from '../../components/Captcha/CaptchaScripts.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 MarkdownFile from '../../components/MarkdownFile.astro';
import UpcomingForm from '../../components/UpcomingForm.astro'; import UpcomingForm from '../../components/UpcomingForm.astro';
import BaseLayout from '../../layouts/BaseLayout.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'; import { generateArticleSchema } from '../../lib/jsonld-schema';
export async function getStaticPaths() { export async function getStaticPaths() {
@ -59,7 +59,7 @@ if (bestPracticeData.schema) {
{ {
!bestPracticeData.isUpcoming && bestPracticeData.jsonUrl && ( !bestPracticeData.isUpcoming && bestPracticeData.jsonUrl && (
<InteractiveRoadmap <FrameRenderer
roadmapId={bestPracticeId} roadmapId={bestPracticeId}
description={bestPracticeData.description} description={bestPracticeData.description}
jsonUrl={bestPracticeData.jsonUrl} jsonUrl={bestPracticeData.jsonUrl}

Loading…
Cancel
Save