diff --git a/src/components/FAQs/Answer.astro b/src/components/FAQs/Answer.astro new file mode 100644 index 000000000..88a9d731a --- /dev/null +++ b/src/components/FAQs/Answer.astro @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/src/components/FAQs/FAQs.astro b/src/components/FAQs/FAQs.astro new file mode 100644 index 000000000..f6df90815 --- /dev/null +++ b/src/components/FAQs/FAQs.astro @@ -0,0 +1,42 @@ +--- +import { markdownToHtml } from '../../lib/markdown'; +import Answer from './Answer.astro'; +import Question from './Question.astro'; + +export type FAQType = { + question: string; + answer: string[]; +}; + +export interface Props { + faqs: FAQType[]; +} + +const { faqs } = Astro.props; + +if (faqs.length === 0) { + return ''; +} +--- + +
+
+
+

Frequently Asked Questions

+
+ +
+ { + faqs.map((faq, questionIndex) => ( + + + {faq.answer.map((answer) => ( +

+ ))} + + + )) + } +

+
+
diff --git a/src/components/FAQs/FAQs.tsx b/src/components/FAQs/FAQs.tsx deleted file mode 100644 index 9da9ef96a..000000000 --- a/src/components/FAQs/FAQs.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useState } from 'react'; -import type { OfficialRoadmapQuestion } from '../../queries/official-roadmap'; -import { Question } from './Question'; -import { guideRenderer } from '../../lib/guide-renderer'; - -type FAQsProps = { - faqs: OfficialRoadmapQuestion[]; -}; - -export function FAQs(props: FAQsProps) { - const { faqs } = props; - - const [activeQuestionIndex, setActiveQuestionIndex] = useState(0); - - return ( -
-
-
-

- Frequently Asked Questions -

-
- -
- {faqs.map((faq, questionIndex) => ( - setActiveQuestionIndex(questionIndex)} - > -
- {guideRenderer.render(faq.description)} -
-
- ))} -
-
-
- ); -} diff --git a/src/components/FAQs/Question.astro b/src/components/FAQs/Question.astro new file mode 100644 index 000000000..e933aac0c --- /dev/null +++ b/src/components/FAQs/Question.astro @@ -0,0 +1,42 @@ +--- +import Icon from '../AstroIcon.astro'; + +export interface Props { + question: string; + isActive?: boolean; +} + +const { question, isActive = false } = Astro.props; +--- + +
+ +
+ +
+
+ + diff --git a/src/components/FAQs/Question.tsx b/src/components/FAQs/Question.tsx deleted file mode 100644 index cb65fb74a..000000000 --- a/src/components/FAQs/Question.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { cn } from '../../lib/classname'; -import { ChevronDownIcon } from '../ReactIcons/ChevronDownIcon'; - -type QuestionProps = { - question: string; - isActive?: boolean; - children: React.ReactNode; - onClick?: () => void; -}; - -export function Question(props: QuestionProps) { - const { question, isActive = false, children, onClick } = props; - - return ( -
- - - {isActive &&
{children}
} -
- ); -} diff --git a/src/components/RelatedRoadmaps.astro b/src/components/RelatedRoadmaps.astro index 1eecaf64a..919e64f3b 100644 --- a/src/components/RelatedRoadmaps.astro +++ b/src/components/RelatedRoadmaps.astro @@ -1,22 +1,69 @@ --- -import { Map } from 'lucide-react'; -import { listOfficialRoadmaps } from '../queries/official-roadmap'; +import { getQuestionGroupsByIds } from '../lib/question-group'; +import { getRoadmapsByIds, type RoadmapFrontmatter } from '../lib/roadmap'; +import { Map, Clipboard } from 'lucide-react'; export interface Props { - relatedRoadmaps: string[]; + roadmap: RoadmapFrontmatter; } -const { relatedRoadmaps } = Astro.props; +const { roadmap } = Astro.props; -const allRoadmaps = await listOfficialRoadmaps(); -const relatedRoadmapsDetails = allRoadmaps.filter((roadmap) => - relatedRoadmaps.includes(roadmap.slug), -); +const relatedRoadmaps = roadmap.relatedRoadmaps || []; +const relatedRoadmapDetails = await getRoadmapsByIds(relatedRoadmaps); + +const relatedQuestions = roadmap.relatedQuestions || []; +const relatedQuestionDetails = await getQuestionGroupsByIds(relatedQuestions); --- +{ + relatedQuestionDetails.length > 0 && ( +
+
+
+ + + Test your Knowledge + + + + More → + +
+ +
+ {relatedQuestionDetails.map((relatedQuestionGroup) => ( + + + {relatedQuestionGroup.title} + + + {relatedQuestionGroup.description} + + + ))} +
+
+
+ ) +} + { relatedRoadmaps.length && ( -
+
@@ -33,15 +80,17 @@ const relatedRoadmapsDetails = allRoadmaps.filter((roadmap) =>
- {relatedRoadmapsDetails.map((relatedRoadmap) => ( + {relatedRoadmapDetails.map((relatedRoadmap) => ( - {relatedRoadmap.title.card} + {relatedRoadmap.frontmatter.briefTitle} + + + {relatedRoadmap.frontmatter.briefDescription} - {relatedRoadmap.description} ))}
diff --git a/src/components/RoadmapHeader.astro b/src/components/RoadmapHeader.astro index 17fff115a..31d42d9d8 100644 --- a/src/components/RoadmapHeader.astro +++ b/src/components/RoadmapHeader.astro @@ -5,7 +5,9 @@ import { Bot, FolderKanbanIcon, MapIcon, + MessageCircle, } from 'lucide-react'; +import { type RoadmapFrontmatter } from '../lib/roadmap'; import LoginPopup from './AuthenticationFlow/LoginPopup.astro'; import { DownloadRoadmapButton } from './DownloadRoadmapButton'; import { MarkFavorite } from './FeaturedItems/MarkFavorite'; @@ -18,16 +20,20 @@ import { PersonalizedRoadmap } from './PersonalizedRoadmap/PersonalizedRoadmap'; export interface Props { title: string; description: string; + note?: string; partner?: { description: string; link: string; linkText: string; }; roadmapId: string; + isUpcoming?: boolean; hasSearch?: boolean; projectCount?: number; coursesCount?: number; hasAIChat?: boolean; + question?: RoadmapFrontmatter['question']; + hasTopics?: boolean; isForkable?: boolean; activeTab?: 'roadmap' | 'projects' | 'courses'; } @@ -37,8 +43,12 @@ const { description, roadmapId, partner, + isUpcoming = false, + note, + hasTopics = false, hasAIChat = false, projectCount = 0, + question, activeTab = 'roadmap', coursesCount = 0, } = Astro.props; diff --git a/src/components/RoadmapTitleQuestion.tsx b/src/components/RoadmapTitleQuestion.tsx index 139a1b58e..bb6daf50f 100644 --- a/src/components/RoadmapTitleQuestion.tsx +++ b/src/components/RoadmapTitleQuestion.tsx @@ -10,12 +10,10 @@ import { useOutsideClick } from '../hooks/use-outside-click'; import { markdownToHtml } from '../lib/markdown'; import { cn } from '../lib/classname'; import { useScrollPosition } from '../hooks/use-scroll-position'; -import type { JSONContent } from '@tiptap/core'; -import { guideRenderer } from '../lib/guide-renderer'; type RoadmapTitleQuestionProps = { question: string; - answer: JSONContent; + answer: string; roadmapId?: string; }; @@ -40,24 +38,24 @@ export function RoadmapTitleQuestion(props: RoadmapTitleQuestionProps) { 'rounded-0 -mx-4 sm:mx-0': isAnswerVisible, // @FIXME: // The line below is to keep the question hidden on mobile devices except for - // the frontend roadmap. This is because we did not use to have the question + // the frontend roadmap. This is because we did not use to have the question // on mobile devices before and we don't want to cause any SEO issues. It will // be enabled on other roadmaps in the future. }, )} > {isAnswerVisible && ( -
+
)}

{ e.preventDefault(); setIsAnswerVisible(!isAnswerVisible); }} > - + {question} @@ -67,7 +65,7 @@ export function RoadmapTitleQuestion(props: RoadmapTitleQuestionProps) {

{ setIsAnswerVisible(false); @@ -97,11 +95,9 @@ export function RoadmapTitleQuestion(props: RoadmapTitleQuestionProps) { )}
- {guideRenderer.render(answer)} -
+ className="bg-gray-100 p-3 text-base [&>h2]:mb-2 [&>h2]:mt-5 [&>h2]:text-[17px] [&>h2]:font-medium [&>p:last-child]:mb-0 [&>p>a]:font-semibold [&>p>a]:underline [&>p>a]:underline-offset-2 [&>p]:mb-3 [&>p]:font-normal [&>p]:leading-relaxed [&>p]:text-gray-800 [&>ul>li]:mb-2 [&>ul>li]:font-normal" + dangerouslySetInnerHTML={{ __html: markdownToHtml(answer, false) }} + >
); diff --git a/src/data/roadmaps/ai-engineer/faqs.astro b/src/data/roadmaps/ai-engineer/faqs.astro index 2a7948d11..f7ee7ddf6 100644 --- a/src/data/roadmaps/ai-engineer/faqs.astro +++ b/src/data/roadmaps/ai-engineer/faqs.astro @@ -1,5 +1,5 @@ --- -import type { FAQType } from '../../../components/FAQs/FAQs'; +import type { FAQType } from '../../../components/FAQs/FAQs.astro'; export const faqs: FAQType[] = [ { @@ -11,13 +11,13 @@ export const faqs: FAQType[] = [ { question: 'What is reinforcement learning?', answer: [ - '[Reinforcement learning](https://towardsdatascience.com/reinforcement-learning-101-e24b50e1d292) (RL) is a type of machine learning where an agent learns to make decisions by interacting with an environment. Unlike traditional supervised learning, RL does not rely on labeled data. Instead, the agent learns by taking actions and receiving feedback in the form of rewards or penalties. Over time, it aims to maximize cumulative rewards by refining its strategy based on past experiences. RL is often used in areas like robotics, game AI, and autonomous systems, where the goal is to develop intelligent behaviors through trial and error.', + '[Reinforcement learning](https://towardsdatascience.com/reinforcement-learning-101-e24b50e1d292) (RL) is a type of machine learning where an agent learns to make decisions by interacting with an environment. Unlike traditional supervised learning, RL does not rely on labeled data. Instead, the agent learns by taking actions and receiving feedback in the form of rewards or penalties. Over time, it aims to maximize cumulative rewards by refining its strategy based on past experiences. RL is often used in areas like robotics, game AI, and autonomous systems, where the goal is to develop intelligent behaviors through trial and error.', ], }, { question: 'Do AI Engineers need a degree?', answer: [ - 'While a degree in computer science, data science, or a related field can provide a solid foundation for becoming an AI engineer, it is not strictly necessary. Many successful AI engineers are self-taught or have gained expertise through online courses, certifications, and hands-on projects.', + 'While a degree in computer science, data science, or a related field can provide a solid foundation for becoming an AI engineer, it is not strictly necessary. Many successful AI engineers are self-taught or have gained expertise through online courses, certifications, and hands-on projects.' ], }, ]; diff --git a/src/data/roadmaps/devops/faqs.astro b/src/data/roadmaps/devops/faqs.astro index 5743d375e..7b8959cbb 100644 --- a/src/data/roadmaps/devops/faqs.astro +++ b/src/data/roadmaps/devops/faqs.astro @@ -1,5 +1,5 @@ --- -import type { FAQType } from '../../../components/FAQs/FAQs'; +import type { FAQType } from '../../../components/FAQs/FAQs.astro'; export const faqs: FAQType[] = [ { diff --git a/src/data/roadmaps/frontend/faqs.astro b/src/data/roadmaps/frontend/faqs.astro index 72a824a39..25759f207 100644 --- a/src/data/roadmaps/frontend/faqs.astro +++ b/src/data/roadmaps/frontend/faqs.astro @@ -1,50 +1,50 @@ --- -import type { FAQType } from '../../../components/FAQs/FAQs'; +import type { FAQType } from '../../../components/FAQs/FAQs.astro'; export const faqs: FAQType[] = [ { question: 'Is Frontend Development really coding?', answer: [ - 'Do frontend developers really code? The answer is yes, absolutely.', - 'The fact that frontend developers are full-time developers who produce an output that is visually appealing (thanks to the designs provided by others) sometimes confuses others, making them believe that frontend developers aren\’t really coding. However, that couldn\’t be further from the truth.', - 'As a frontend developer, you\’ll be coding all the time.', - 'While in some companies, the frontend developer is also a skilled designer or UX engineer, those are not the typical profiles. As a frontend dev, your learning focus should be coding-related (i.e coding best practices, software design patterns, frontend architecture, etc).', + "Do frontend developers really code? The answer is yes, absolutely.", + "The fact that frontend developers are full-time developers who produce an output that is visually appealing (thanks to the designs provided by others) sometimes confuses others, making them believe that frontend developers aren\’t really coding. However, that couldn\’t be further from the truth.", + "As a frontend developer, you\’ll be coding all the time.", + "While in some companies, the frontend developer is also a skilled designer or UX engineer, those are not the typical profiles. As a frontend dev, your learning focus should be coding-related (i.e coding best practices, software design patterns, frontend architecture, etc).", ], }, { question: 'Is Frontend Developer a good career?', answer: [ - 'As the web space and technology in general continue to evolve, the role of frontend developers becomes more relevant. In the end, providing a web version of an application has quite a lot of benefits, making it the obvious version (unless there is a specific requirement forcing for native development) for most systems, meaning that frontend developers have the potential to be involved in all types of projects and companies.', - 'This renders the frontend developer career one of the most versatile and in-demand paths in the web tech industry.', + "As the web space and technology in general continue to evolve, the role of frontend developers becomes more relevant. In the end, providing a web version of an application has quite a lot of benefits, making it the obvious version (unless there is a specific requirement forcing for native development) for most systems, meaning that frontend developers have the potential to be involved in all types of projects and companies.", + "This renders the frontend developer career one of the most versatile and in-demand paths in the web tech industry.", ], }, { question: 'How to prepare for a frontend developer interview?', answer: [ - 'If you\’re looking to prepare for a frontend developer interview, make sure you\’ve tackled the fundamentals of the technologies you\’ll work with.', - 'And while that is one of the most impactful things you can do for your frontend career, consider focusing on the following points as well:', - '1. **Master the Fundamentals**: Ensure a solid understanding of HTML, CSS, and JavaScript, as they are crucial for the role of frontend developer.', - '2. **Practice Coding**: Improve your skills through mini-projects or coding challenges on platforms like LeetCode, especially those focused on front-end development. Using these skills to create a [web developer portfolio](https://roadmap.sh/frontend/web-developer-portfolio) can help you in many ways.', - '3. **Learn Modern Frameworks**: Familiarize yourself with popular frameworks like React, Angular, or Vue.js.', - '4. **Know Your Tools**: Be comfortable with version control systems, testing, and build tools, which are vital for all types of development, including frontend.', - '5. **Understand UI/UX Principles**: Learn about accessibility, responsive design, and intuitive interfaces to stand out.', - '6. **Research the Company**: Show interest by learning about the company\’s business and products.', - '7. **Enhance Communication Skills**: Good communication skills are essential for success in any role, so make sure to work on them.', + "If you\’re looking to prepare for a frontend developer interview, make sure you\’ve tackled the fundamentals of the technologies you\’ll work with.", + "And while that is one of the most impactful things you can do for your frontend career, consider focusing on the following points as well:", + "1. **Master the Fundamentals**: Ensure a solid understanding of HTML, CSS, and JavaScript, as they are crucial for the role of frontend developer.", + "2. **Practice Coding**: Improve your skills through mini-projects or coding challenges on platforms like LeetCode, especially those focused on front-end development. Using these skills to create a [web developer portfolio](https://roadmap.sh/frontend/web-developer-portfolio) can help you in many ways.", + "3. **Learn Modern Frameworks**: Familiarize yourself with popular frameworks like React, Angular, or Vue.js.", + "4. **Know Your Tools**: Be comfortable with version control systems, testing, and build tools, which are vital for all types of development, including frontend.", + "5. **Understand UI/UX Principles**: Learn about accessibility, responsive design, and intuitive interfaces to stand out.", + "6. **Research the Company**: Show interest by learning about the company\’s business and products.", + "7. **Enhance Communication Skills**: Good communication skills are essential for success in any role, so make sure to work on them.", ], }, { question: 'How is Frontend Development different from Backend Development?', answer: [ - 'The main difference between frontend development and backend development is that frontend development focuses on the UI, while [backend development](https://roadmap.sh/backend) focuses more on the server-side logic.', - 'You see, frontend development works with the user interface and user experience, dealing with the design, layout, and interactivity of a website or application using HTML, CSS, and JavaScript (or TypeScript).', - 'On the other hand, backend development handles the server-side logic, databases, and application functionality, ensuring data is processed and served correctly. The tech stack for backend development is much bigger with more options, such as Python, Java, or Node.js.', - 'Both options are equally interesting and challenging, so it\’s not really a question of backend vs frontend, but instead of understanding where you feel more comfortable and what type of systems you enjoy creating.', + "The main difference between frontend development and backend development is that frontend development focuses on the UI, while [backend development](https://roadmap.sh/backend) focuses more on the server-side logic.", + "You see, frontend development works with the user interface and user experience, dealing with the design, layout, and interactivity of a website or application using HTML, CSS, and JavaScript (or TypeScript).", + "On the other hand, backend development handles the server-side logic, databases, and application functionality, ensuring data is processed and served correctly. The tech stack for backend development is much bigger with more options, such as Python, Java, or Node.js.", + "Both options are equally interesting and challenging, so it\’s not really a question of backend vs frontend, but instead of understanding where you feel more comfortable and what type of systems you enjoy creating.", ], }, { question: 'What are the job titles of a Frontend Developer?', answer: [ - 'Front-end developers are also known as front-end engineers, front-end web developers, JavaScript Developers, HTML/CSS Developer, front-end web designers, and front-end web architects.', + "Front-end developers are also known as front-end engineers, front-end web developers, JavaScript Developers, HTML/CSS Developer, front-end web designers, and front-end web architects.", "Each of these roles mostly encompasses the same front-end development skills but requires different levels of expertise in different [front-end development skills](https://roadmap.sh/frontend/developer-skills). It's better to look at the job description to get an idea about the job requirements.", ], }, @@ -57,16 +57,16 @@ export const faqs: FAQType[] = [ { question: 'How long does it take to become a Frontend Developer?', answer: [ - 'The amount of time it takes to become a frontend developer can vary depending on several factors, such as your learning pace, previous experience, and the amount of time you are able to dedicate to learning.', + "The amount of time it takes to become a frontend developer can vary depending on several factors, such as your learning pace, previous experience, and the amount of time you are able to dedicate to learning.", "However, to give you a rough idea, if you are a complete beginner, it could take you anywhere from 3 to 6 months to get a job as an entry-level frontend developer. If you are already familiar with some of the frontend technologies, it could take you anywhere from 1 to 3 months. What's important is to practice as much as you can while you are learning i.e., by building as many projects as you can. You should also participate in online communities and ask for feedback from more experienced developers to accelerate your learning process.", ], }, { question: 'What are the Frontend Developer salaries?', answer: [ - 'Frontend developer salaries can vary depending on factors such as location, experience, and company size. According to data from Glassdoor, the average base salary for a frontend developer in the United States is around $80,000 per year. However, this number can vary greatly depending on location, with the highest-paying cities such as San Francisco, Seattle, and New York having an average salary of $110,000 to $130,000.', + "Frontend developer salaries can vary depending on factors such as location, experience, and company size. According to data from Glassdoor, the average base salary for a frontend developer in the United States is around $80,000 per year. However, this number can vary greatly depending on location, with the highest-paying cities such as San Francisco, Seattle, and New York having an average salary of $110,000 to $130,000.", "It's important to keep in mind that these are just averages, and salaries can vary greatly depending on factors such as experience level, specific skills, and the company you work for. With more experience and specific skills, you can expect to earn more.", - 'It is worth looking at a range of resources, including salary surveys and job boards, to get a general understanding of the current market in your location and experience level. Also, try reaching out to other professionals in the field and understanding their experience and salary ranges.', + "It is worth looking at a range of resources, including salary surveys and job boards, to get a general understanding of the current market in your location and experience level. Also, try reaching out to other professionals in the field and understanding their experience and salary ranges.", ], }, { @@ -79,8 +79,8 @@ export const faqs: FAQType[] = [ { question: 'What are Frontend Frameworks?', answer: [ - '[Frontend frameworks](https://roadmap.sh/frontend/frameworks) are collections of tools and libraries that help developers build web applications more efficiently. They provide a structure for the code, making it easier to build and maintain complex applications.', - 'Some popular frontend frameworks include React, Angular, and Vue.js. These frameworks provide a set of tools and libraries that help developers build user interfaces, manage state, and interact with APIs.', + "[Frontend frameworks](https://roadmap.sh/frontend/frameworks) are collections of tools and libraries that help developers build web applications more efficiently. They provide a structure for the code, making it easier to build and maintain complex applications.", + "Some popular frontend frameworks include React, Angular, and Vue.js. These frameworks provide a set of tools and libraries that help developers build user interfaces, manage state, and interact with APIs.", ], }, ]; diff --git a/src/data/roadmaps/full-stack/faqs.astro b/src/data/roadmaps/full-stack/faqs.astro index 26c36472e..d1ae72244 100644 --- a/src/data/roadmaps/full-stack/faqs.astro +++ b/src/data/roadmaps/full-stack/faqs.astro @@ -1,5 +1,5 @@ --- -import type { FAQType } from '../../../components/FAQs/FAQs'; +import type { FAQType } from '../../../components/FAQs/FAQs.astro'; export const faqs: FAQType[] = [ { diff --git a/src/lib/guide-renderer.tsx b/src/lib/guide-renderer.tsx index e13c7dd8d..78f05e29c 100644 --- a/src/lib/guide-renderer.tsx +++ b/src/lib/guide-renderer.tsx @@ -30,12 +30,10 @@ export interface MarkType { attrs?: Record | undefined; } -export type GuideRendererOptions = {}; - export class GuideRenderer { private marksOrder = ['underline', 'bold', 'italic', 'textStyle', 'link']; - render(content: JSONContent) { + render(content: JSONContent): JSX.Element[] { const nodes = content.content || []; const jsxNodes = nodes .map((node, index) => { diff --git a/src/lib/jsonld-schema.ts b/src/lib/jsonld-schema.ts index 8f9823b6e..a3f8ca1fa 100644 --- a/src/lib/jsonld-schema.ts +++ b/src/lib/jsonld-schema.ts @@ -1,5 +1,4 @@ -import type { OfficialRoadmapQuestion } from '../queries/official-roadmap'; -import { renderMarkdownFromJson } from './markdown-renderer'; +import type { FAQType } from '../components/FAQs/FAQs.astro'; type ArticleSchemaProps = { url: string; @@ -42,16 +41,16 @@ export function generateArticleSchema(article: ArticleSchemaProps) { }; } -export function generateFAQSchema(faqs: OfficialRoadmapQuestion[]) { +export function generateFAQSchema(faqs: FAQType[]) { return { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faqs.map((faq) => ({ '@type': 'Question', - name: faq.title, + name: faq.question, acceptedAnswer: { '@type': 'Answer', - text: renderMarkdownFromJson(faq.description, { join: ' ' }), + text: faq.answer.join(' '), }, })), }; diff --git a/src/lib/markdown-renderer.tsx b/src/lib/markdown-renderer.tsx deleted file mode 100644 index 24924cfb7..000000000 --- a/src/lib/markdown-renderer.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import type { JSONContent } from '@tiptap/core'; - -export type MarkdownRendererOptions = { - join?: string; -}; - -export class MarkdownRenderer { - private marksOrder = ['underline', 'bold', 'italic', 'textStyle', 'link']; - - render(content: JSONContent, options: MarkdownRendererOptions = {}): string { - const nodes = content.content || []; - return nodes - .map((node) => this.renderNode(node)) - .join(options?.join || '\n\n'); - } - - private renderNode(node: JSONContent): string { - const type = node.type || ''; - - if (type in this) { - // @ts-expect-error dynamic lookup - return this[type]?.(node); - } - - console.warn(`Node type "${type}" is not supported.`); - return ''; - } - - private getText(node: JSONContent): string { - if (node.type === 'text') return node.text || ''; - if (node.content) - return node.content.map((child) => this.getText(child)).join(''); - return ''; - } - - private content(node: JSONContent): string { - return (node.content || []).map((child) => this.renderNode(child)).join(''); - } - - private renderMark(node: JSONContent): string { - let text = node.text || ''; - let marks = node.marks || []; - marks.sort( - (a, b) => - this.marksOrder.indexOf(a.type) - this.marksOrder.indexOf(b.type), - ); - - return marks.reduce((acc, mark) => { - if (mark.type === 'bold') return `**${acc}**`; - if (mark.type === 'italic') return `*${acc}*`; - if (mark.type === 'underline') return `_${acc}_`; // fallback since markdown has no underline - if (mark.type === 'code') return `\`${acc}\``; - if (mark.type === 'link') return `[${acc}](${mark.attrs?.href})`; - return acc; - }, text); - } - - // ---- Nodes ---- - private paragraph(node: JSONContent): string { - return this.content(node); - } - - private text(node: JSONContent): string { - return node.marks ? this.renderMark(node) : node.text || ''; - } - - private heading(node: JSONContent): string { - const level = node.attrs?.level || 1; - const prefix = '#'.repeat(level); - return `${prefix} ${this.content(node)}`; - } - - private bulletList(node: JSONContent): string { - return (node.content || []) - .map((child) => `- ${this.renderNode(child)}`) - .join('\n'); - } - - private orderedList(node: JSONContent): string { - return (node.content || []) - .map((child, i) => `${i + 1}. ${this.renderNode(child)}`) - .join('\n'); - } - - private listItem(node: JSONContent): string { - return this.content(node); - } - - private blockquote(node: JSONContent): string { - return this.content(node) - .split('\n') - .map((line) => `> ${line}`) - .join('\n'); - } - - private codeBlock(node: JSONContent): string { - const code = this.getText(node); - const language = node.attrs?.language || ''; - return `\`\`\`${language}\n${code}\n\`\`\``; - } - - private horizontalRule(): string { - return `---`; - } - - private image(node: JSONContent): string { - const { src, alt } = node.attrs || {}; - return `![${alt || ''}](${src})`; - } - - private table(node: JSONContent): string { - const rows = (node.content || []).filter((n) => n.type === 'tableRow'); - return rows.map((row) => this.renderNode(row)).join('\n'); - } - - private tableRow(node: JSONContent): string { - return `| ${this.content(node)} |`; - } - - private tableHeader(node: JSONContent): string { - return this.content(node); - } - - private tableCell(node: JSONContent): string { - return this.content(node); - } -} - -export function renderMarkdownFromJson( - json: JSONContent, - options: MarkdownRendererOptions = {}, -) { - return new MarkdownRenderer().render(json, options); -} diff --git a/src/pages/[roadmapId]/ai.astro b/src/pages/[roadmapId]/ai.astro new file mode 100644 index 000000000..75e8c2215 --- /dev/null +++ b/src/pages/[roadmapId]/ai.astro @@ -0,0 +1,43 @@ +--- +import { CheckSubscriptionVerification } from '../../components/Billing/CheckSubscriptionVerification'; +import { RoadmapAIChat } from '../../components/RoadmapAIChat/RoadmapAIChat'; +import SkeletonLayout from '../../layouts/SkeletonLayout.astro'; +import { AITutorLayout } from '../../components/AITutor/AITutorLayout'; +import { getRoadmapById, getRoadmapIds } from '../../lib/roadmap'; + +type Props = { + roadmapId: string; +}; + +export const prerender = false; + +export async function getStaticPaths() { + const roadmapIds = await getRoadmapIds(); + + return roadmapIds.map((roadmapId) => ({ + params: { roadmapId }, + })); +} + +const { roadmapId } = Astro.params as Props; + +const roadmapDetail = await getRoadmapById(roadmapId); + +const canonicalUrl = `https://roadmap.sh/${roadmapId}/ai`; +const roadmapBriefTitle = roadmapDetail.frontmatter.briefTitle; +--- + + + + + + + diff --git a/src/pages/[roadmapId]/courses.astro b/src/pages/[roadmapId]/courses.astro index 001f62eb7..227514a3d 100644 --- a/src/pages/[roadmapId]/courses.astro +++ b/src/pages/[roadmapId]/courses.astro @@ -70,6 +70,7 @@ const courses = roadmapData.courses || []; ({ - params: { roadmapId: roadmap.slug }, + return roadmapIds.map((roadmapId) => ({ + params: { roadmapId }, })); } @@ -37,67 +35,60 @@ interface Params extends Record { } const { roadmapId } = Astro.params as Params; -const roadmapData = await officialRoadmapDetails(roadmapId); -if (!roadmapData) { - return Astro.rewrite('/404'); -} +const roadmapFile = await import( + `../../data/roadmaps/${roadmapId}/${roadmapId}.md` +); +const { faqs: roadmapFAQs = [] } = await import( + `../../data/roadmaps/${roadmapId}/faqs.astro` +); +const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter; let jsonLdSchema = []; -const datePublished = DateTime.fromJSDate( - new Date(roadmapData?.createdAt), -).toFormat('yyyy-MM-dd'); -const dateModified = DateTime.fromJSDate( - new Date(roadmapData?.updatedAt), -).toFormat('yyyy-MM-dd'); +if (roadmapData.schema) { + const roadmapSchema = roadmapData.schema; + jsonLdSchema.push( + generateArticleSchema({ + url: `https://roadmap.sh/${roadmapId}`, + headline: roadmapSchema.headline, + description: roadmapSchema.description, + datePublished: roadmapSchema.datePublished, + dateModified: roadmapSchema.dateModified, + imageUrl: roadmapSchema.imageUrl, + }), + ); +} -const baseUrl = import.meta.env.DEV - ? `http://localhost:8080` - : `https://roadmap.sh`; - -jsonLdSchema.push( - generateArticleSchema({ - url: `https://roadmap.sh/${roadmapId}`, - headline: roadmapData?.seo?.title || roadmapData?.title?.page, - description: roadmapData?.description, - datePublished, - dateModified, - imageUrl: `${baseUrl}/roadmaps/${roadmapId}.png`, - }), -); +if (roadmapFAQs.length) { + jsonLdSchema.push(generateFAQSchema(roadmapFAQs as unknown as FAQType[])); +} const ogImageUrl = - roadmapData?.openGraph?.image || + roadmapData?.seo?.ogImageUrl || getOpenGraphImageUrl({ group: 'roadmap', resourceId: roadmapId, }); -const question = roadmapData?.questions?.find( - (question) => question.type === 'main', -); -const faqs = - roadmapData?.questions?.filter((question) => question.type === 'faq') || []; -if (faqs.length) { - jsonLdSchema.push(generateFAQSchema(faqs)); -} - +const question = roadmapData?.question; +const note = roadmapData.note; const projects = await getProjectsByRoadmapId(roadmapId); -// const courses = roadmapData.courses || []; +const courses = roadmapData.courses || []; --- +
@@ -147,23 +142,33 @@ const projects = await getProjectsByRoadmapId(roadmapId); - + { + roadmapData?.renderer === 'editor' ? ( + + ) : ( + + ) + }
@@ -175,8 +180,9 @@ const projects = await getProjectsByRoadmapId(roadmapId); ) } - - + + +
diff --git a/src/pages/[roadmapId]/projects.astro b/src/pages/[roadmapId]/projects.astro index bde9f23e2..c434cc4bc 100644 --- a/src/pages/[roadmapId]/projects.astro +++ b/src/pages/[roadmapId]/projects.astro @@ -76,9 +76,13 @@ const { response: userCounts } = ( - `/v1-official-roadmap/${roadmapSlug}`, - ); - - return roadmap; - } catch (error) { - if (FetchError.isFetchError(error) && error.status === 404) { - return null; - } - - throw error; - } -} - -export async function listOfficialRoadmaps() { - try { - const roadmaps = await httpGet( - `/v1-list-official-roadmaps`, - ); - - return roadmaps; - } catch (error) { - if (FetchError.isFetchError(error) && error.status === 404) { - return []; - } - - throw error; - } -}